mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-11-03 22:37:27 +00:00 
			
		
		
		
	Compare commits
	
		
			576 Commits
		
	
	
		
			v0.7.1
			...
			juan-canha
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					901659cc4f | ||
| 
						 | 
					3cf850ea0e | ||
| 
						 | 
					1b0bc87b60 | ||
| 
						 | 
					5d3b993737 | ||
| 
						 | 
					917fd8e3f3 | ||
| 
						 | 
					6ac2cd490b | ||
| 
						 | 
					8100190978 | ||
| 
						 | 
					7c36ff8331 | ||
| 
						 | 
					255a518565 | ||
| 
						 | 
					2d595350c6 | ||
| 
						 | 
					ca92d41d9e | ||
| 
						 | 
					3f8e795c61 | ||
| 
						 | 
					9927e79900 | ||
| 
						 | 
					dbef9f0a35 | ||
| 
						 | 
					c889664c31 | ||
| 
						 | 
					7a60cd0bab | ||
| 
						 | 
					964350d3c7 | ||
| 
						 | 
					b57d9f05af | ||
| 
						 | 
					b26799f9fc | ||
| 
						 | 
					daeffd27b5 | ||
| 
						 | 
					f87eb46549 | ||
| 
						 | 
					da7cd28734 | ||
| 
						 | 
					31e0bb69cc | ||
| 
						 | 
					5c248cca2a | ||
| 
						 | 
					f83eb018e8 | ||
| 
						 | 
					317a250917 | ||
| 
						 | 
					aaa5e1fc1f | ||
| 
						 | 
					1bc4263a46 | ||
| 
						 | 
					e191af1fb0 | ||
| 
						 | 
					7c0a9bcc02 | ||
| 
						 | 
					d894f62607 | ||
| 
						 | 
					2758038858 | ||
| 
						 | 
					661fb91861 | ||
| 
						 | 
					3c551eb923 | ||
| 
						 | 
					5bf4bda6de | ||
| 
						 | 
					49ae016f08 | ||
| 
						 | 
					89957aed76 | ||
| 
						 | 
					233025ddcc | ||
| 
						 | 
					11dd8952d8 | ||
| 
						 | 
					d7c83613df | ||
| 
						 | 
					a456093f13 | ||
| 
						 | 
					dd4851498a | ||
| 
						 | 
					f72b02a0f4 | ||
| 
						 | 
					63a0c78fc5 | ||
| 
						 | 
					fd39cc9fd9 | ||
| 
						 | 
					70f45d468d | ||
| 
						 | 
					212b01092c | ||
| 
						 | 
					36d641801b | ||
| 
						 | 
					bd278268f6 | ||
| 
						 | 
					abe31e30e0 | ||
| 
						 | 
					314d8d8763 | ||
| 
						 | 
					ed0792e8f8 | ||
| 
						 | 
					90765bf90b | ||
| 
						 | 
					f1ba7765ee | ||
| 
						 | 
					27c7a0264a | ||
| 
						 | 
					8e806dc04f | ||
| 
						 | 
					8ec6b5ed6a | ||
| 
						 | 
					4ef4ec5d42 | ||
| 
						 | 
					2f523b845b | ||
| 
						 | 
					1c416f39d3 | ||
| 
						 | 
					1de0eff7b3 | ||
| 
						 | 
					f8a39b0908 | ||
| 
						 | 
					d69e4635be | ||
| 
						 | 
					4b7d594502 | ||
| 
						 | 
					896b7055c1 | ||
| 
						 | 
					0f65e4c9f3 | ||
| 
						 | 
					e9971eb882 | ||
| 
						 | 
					beb60d4074 | ||
| 
						 | 
					4440d23584 | ||
| 
						 | 
					aca67cec29 | ||
| 
						 | 
					75a953aa73 | ||
| 
						 | 
					15a0af8410 | ||
| 
						 | 
					9f811336e4 | ||
| 
						 | 
					a07faf6d50 | ||
| 
						 | 
					f098ed507f | ||
| 
						 | 
					80c36b96bc | ||
| 
						 | 
					630cf59cfb | ||
| 
						 | 
					165eb5d9cd | ||
| 
						 | 
					d12e970af5 | ||
| 
						 | 
					cf18c5d90d | ||
| 
						 | 
					0497696dcf | ||
| 
						 | 
					d007bd9bf6 | ||
| 
						 | 
					5838b085c7 | ||
| 
						 | 
					58b6ad841e | ||
| 
						 | 
					fc937e3ec8 | ||
| 
						 | 
					8652c7ecdf | ||
| 
						 | 
					c882235eff | ||
| 
						 | 
					d6c5239f9e | ||
| 
						 | 
					4b2db3f720 | ||
| 
						 | 
					9736777828 | ||
| 
						 | 
					d3194fba19 | ||
| 
						 | 
					fa29f9794d | ||
| 
						 | 
					07915002bb | ||
| 
						 | 
					fbcc06dcda | ||
| 
						 | 
					7413a3a257 | ||
| 
						 | 
					e6d2255291 | ||
| 
						 | 
					2840ec3f87 | ||
| 
						 | 
					05cd863ebf | ||
| 
						 | 
					20961afb62 | ||
| 
						 | 
					1256095e25 | ||
| 
						 | 
					f073c79b8d | ||
| 
						 | 
					ac9e4aa1a0 | ||
| 
						 | 
					915f35b1e6 | ||
| 
						 | 
					4fe74057f9 | ||
| 
						 | 
					5a1ec033bb | ||
| 
						 | 
					6801e39f97 | ||
| 
						 | 
					f6f383751f | ||
| 
						 | 
					43ed564a6e | ||
| 
						 | 
					7b3364c356 | ||
| 
						 | 
					58a7fc09e5 | ||
| 
						 | 
					01c053702d | ||
| 
						 | 
					a935fe7dc2 | ||
| 
						 | 
					c9825fa016 | ||
| 
						 | 
					9eb9207348 | ||
| 
						 | 
					6b171e69db | ||
| 
						 | 
					5b0ee89e34 | ||
| 
						 | 
					8bd3ddc7fd | ||
| 
						 | 
					984ae95576 | ||
| 
						 | 
					f77cced7f3 | ||
| 
						 | 
					57787f1bc7 | ||
| 
						 | 
					9419f905df | ||
| 
						 | 
					001fd893c1 | ||
| 
						 | 
					babe4b4b31 | ||
| 
						 | 
					201f39442e | ||
| 
						 | 
					47f6aff561 | ||
| 
						 | 
					cef9a92cb6 | ||
| 
						 | 
					2253e4ead7 | ||
| 
						 | 
					2f628f8564 | ||
| 
						 | 
					23cd52885b | ||
| 
						 | 
					181419ae28 | ||
| 
						 | 
					a81ad0fef2 | ||
| 
						 | 
					d220cedfeb | ||
| 
						 | 
					e72564162b | ||
| 
						 | 
					c98d05270e | ||
| 
						 | 
					3e3803ed85 | ||
| 
						 | 
					c8d8e566f8 | ||
| 
						 | 
					712cba57b8 | ||
| 
						 | 
					c9e45d4991 | ||
| 
						 | 
					e9edc0d15c | ||
| 
						 | 
					b99a09c88a | ||
| 
						 | 
					5c95fe7af1 | ||
| 
						 | 
					17f2ebb753 | ||
| 
						 | 
					fc67f680ee | ||
| 
						 | 
					88879257e6 | ||
| 
						 | 
					fff45e1431 | ||
| 
						 | 
					934d8a6123 | ||
| 
						 | 
					defe9b6e95 | ||
| 
						 | 
					4c5ccc001a | ||
| 
						 | 
					de5c2ecb95 | ||
| 
						 | 
					dbb95aef3a | ||
| 
						 | 
					c9ae2ffef3 | ||
| 
						 | 
					86af2a2c4f | ||
| 
						 | 
					37ea6cf804 | ||
| 
						 | 
					a9c685c6a4 | ||
| 
						 | 
					7765e85336 | ||
| 
						 | 
					7af50c51f6 | ||
| 
						 | 
					19b30d55ec | ||
| 
						 | 
					eddda8146e | ||
| 
						 | 
					1a0b91a58f | ||
| 
						 | 
					1b94ada709 | ||
| 
						 | 
					1966b0a862 | ||
| 
						 | 
					8ced6a730a | ||
| 
						 | 
					6cd1e60e79 | ||
| 
						 | 
					be691e4230 | ||
| 
						 | 
					07b23109f9 | ||
| 
						 | 
					32769a2b0b | ||
| 
						 | 
					280977cb62 | ||
| 
						 | 
					ddceec68a2 | ||
| 
						 | 
					b961fd1c07 | ||
| 
						 | 
					342b960f63 | ||
| 
						 | 
					f965bf456a | ||
| 
						 | 
					69be38110f | ||
| 
						 | 
					3800e19418 | ||
| 
						 | 
					e29ed58a1c | ||
| 
						 | 
					11bfcd4bef | ||
| 
						 | 
					fbc2e9a4db | ||
| 
						 | 
					7814786957 | ||
| 
						 | 
					542776fd2e | ||
| 
						 | 
					815ee3dc7e | ||
| 
						 | 
					376e720f4b | ||
| 
						 | 
					b224c8939b | ||
| 
						 | 
					0ecac98cff | ||
| 
						 | 
					1416f57d0b | ||
| 
						 | 
					65c7e41c53 | ||
| 
						 | 
					c8cc673ad5 | ||
| 
						 | 
					656dbe2fc2 | ||
| 
						 | 
					a4ee7127ee | ||
| 
						 | 
					fee21a7b17 | ||
| 
						 | 
					32fd8dc636 | ||
| 
						 | 
					2c8f444d42 | ||
| 
						 | 
					bd8b587c5b | ||
| 
						 | 
					4c954b79df | ||
| 
						 | 
					b7fffbcf73 | ||
| 
						 | 
					0829800b65 | ||
| 
						 | 
					d7cfc76636 | ||
| 
						 | 
					311030474d | ||
| 
						 | 
					ec69e668ff | ||
| 
						 | 
					f18910f490 | ||
| 
						 | 
					540ad48d61 | ||
| 
						 | 
					540c745069 | ||
| 
						 | 
					c5b8eec33a | ||
| 
						 | 
					bece335a64 | ||
| 
						 | 
					3aabb5028d | ||
| 
						 | 
					732bc9809a | ||
| 
						 | 
					d77b484e55 | ||
| 
						 | 
					43564bf380 | ||
| 
						 | 
					88c71f6e9c | ||
| 
						 | 
					47e8605f50 | ||
| 
						 | 
					9466a8c0dd | ||
| 
						 | 
					d878270bc6 | ||
| 
						 | 
					3b38c4818f | ||
| 
						 | 
					62c967526f | ||
| 
						 | 
					6e5a44798b | ||
| 
						 | 
					1fbfe2507b | ||
| 
						 | 
					d6a3aab68a | ||
| 
						 | 
					9fdfd1b5a6 | ||
| 
						 | 
					f4e763bd9c | ||
| 
						 | 
					fbfff2a4e4 | ||
| 
						 | 
					1c93932737 | ||
| 
						 | 
					cba29511bc | ||
| 
						 | 
					1d655a4ddb | ||
| 
						 | 
					ca94513630 | ||
| 
						 | 
					971d4a5439 | ||
| 
						 | 
					f3dcbd9081 | ||
| 
						 | 
					29c53af843 | ||
| 
						 | 
					8d24087faa | ||
| 
						 | 
					95df8e5af4 | ||
| 
						 | 
					8a1da777b0 | ||
| 
						 | 
					44555da00f | ||
| 
						 | 
					46bd5d51cc | ||
| 
						 | 
					3964d300aa | ||
| 
						 | 
					d6280e6d89 | ||
| 
						 | 
					4a2a47f551 | ||
| 
						 | 
					ae51930c9c | ||
| 
						 | 
					fb33455bea | ||
| 
						 | 
					28c703daf7 | ||
| 
						 | 
					0246a5da19 | ||
| 
						 | 
					840d17c67b | ||
| 
						 | 
					9f22e94cf7 | ||
| 
						 | 
					97ebecd84a | ||
| 
						 | 
					96b9bb68e3 | ||
| 
						 | 
					c5a5d3761d | ||
| 
						 | 
					c147403b1c | ||
| 
						 | 
					a2723452c2 | ||
| 
						 | 
					cb3488276d | ||
| 
						 | 
					43419c27cf | ||
| 
						 | 
					0f0c399dd5 | ||
| 
						 | 
					cb46497346 | ||
| 
						 | 
					850c640368 | ||
| 
						 | 
					60e455b36d | ||
| 
						 | 
					af896c85ea | ||
| 
						 | 
					6a7bb5ea5b | ||
| 
						 | 
					3b6f2ad37e | ||
| 
						 | 
					101eebdd95 | ||
| 
						 | 
					830c36818e | ||
| 
						 | 
					39e995213f | ||
| 
						 | 
					37a053722d | ||
| 
						 | 
					12fcf3b0cb | ||
| 
						 | 
					43ad9c1c71 | ||
| 
						 | 
					4f9207a868 | ||
| 
						 | 
					3d1f589bc1 | ||
| 
						 | 
					ae436a3b84 | ||
| 
						 | 
					202bb44c76 | ||
| 
						 | 
					041c609ff0 | ||
| 
						 | 
					712b504168 | ||
| 
						 | 
					bc9f0d468f | ||
| 
						 | 
					2d20077c08 | ||
| 
						 | 
					f61deda4e8 | ||
| 
						 | 
					8203fa50ae | ||
| 
						 | 
					c5eab0fd9c | ||
| 
						 | 
					40e71238ac | ||
| 
						 | 
					9d75b207d1 | ||
| 
						 | 
					9b52c396d3 | ||
| 
						 | 
					2759727984 | ||
| 
						 | 
					e230d640cb | ||
| 
						 | 
					d69688697c | ||
| 
						 | 
					9f7ec62b18 | ||
| 
						 | 
					b1a02918ff | ||
| 
						 | 
					ec05f6737a | ||
| 
						 | 
					da5db6477b | ||
| 
						 | 
					0f580efb2b | ||
| 
						 | 
					ff23ee508b | ||
| 
						 | 
					2819faeb6f | ||
| 
						 | 
					d205e882f6 | ||
| 
						 | 
					3f40441b0a | ||
| 
						 | 
					6185f20ec9 | ||
| 
						 | 
					6a61989eb4 | ||
| 
						 | 
					d658a069cd | ||
| 
						 | 
					25688dbe8e | ||
| 
						 | 
					98362b9687 | ||
| 
						 | 
					4c31c96891 | ||
| 
						 | 
					219209c6ca | ||
| 
						 | 
					eff9fc51cb | ||
| 
						 | 
					2ba23ee80d | ||
| 
						 | 
					0f83f8f5c2 | ||
| 
						 | 
					4ba3a3f2a9 | ||
| 
						 | 
					db486a48aa | ||
| 
						 | 
					2cab1195e8 | ||
| 
						 | 
					ce75f09210 | ||
| 
						 | 
					a8fed1b69b | ||
| 
						 | 
					62ca2020d8 | ||
| 
						 | 
					f65cf8880e | ||
| 
						 | 
					c8d4a3deb3 | ||
| 
						 | 
					d5e2a45034 | ||
| 
						 | 
					2465f2ce1c | ||
| 
						 | 
					f2001bcbb1 | ||
| 
						 | 
					d5afb3eb2e | ||
| 
						 | 
					c711cb7922 | ||
| 
						 | 
					e45e0316f6 | ||
| 
						 | 
					08ab512f4c | ||
| 
						 | 
					f2bf09bf96 | ||
| 
						 | 
					75e2b1c131 | ||
| 
						 | 
					0b7ef16a41 | ||
| 
						 | 
					247eec396c | ||
| 
						 | 
					46c7fa9838 | ||
| 
						 | 
					b3fb2c7130 | ||
| 
						 | 
					c3ec3f28bd | ||
| 
						 | 
					0a8ee721e8 | ||
| 
						 | 
					8d7cf32988 | ||
| 
						 | 
					655ecebaa5 | ||
| 
						 | 
					8fc0fa99d3 | ||
| 
						 | 
					69e8adc1cc | ||
| 
						 | 
					6b3396e01b | ||
| 
						 | 
					a95b52acd0 | ||
| 
						 | 
					47553b6def | ||
| 
						 | 
					e4a549ed30 | ||
| 
						 | 
					dd2148bb92 | ||
| 
						 | 
					d8b9d86896 | ||
| 
						 | 
					889bd4bfc5 | ||
| 
						 | 
					13fc903b2b | ||
| 
						 | 
					8c8dbfed72 | ||
| 
						 | 
					2b669cf35c | ||
| 
						 | 
					5a2d892b85 | ||
| 
						 | 
					37a7c318d5 | ||
| 
						 | 
					43873efcab | ||
| 
						 | 
					bb28e5aa8e | ||
| 
						 | 
					c17261cd25 | ||
| 
						 | 
					49e56cc226 | ||
| 
						 | 
					84ad6cf356 | ||
| 
						 | 
					b96526da31 | ||
| 
						 | 
					cb14452df3 | ||
| 
						 | 
					d54b9a6d6c | ||
| 
						 | 
					6285c2db3b | ||
| 
						 | 
					3453293c79 | ||
| 
						 | 
					fb32cb0d78 | ||
| 
						 | 
					baccb75256 | ||
| 
						 | 
					5c39c1c93d | ||
| 
						 | 
					48cc315fc8 | ||
| 
						 | 
					ea8da6811a | ||
| 
						 | 
					dbda48c16d | ||
| 
						 | 
					bc710b5c6e | ||
| 
						 | 
					b85d40b1b3 | ||
| 
						 | 
					069c02ddcc | ||
| 
						 | 
					1f6d77fc28 | ||
| 
						 | 
					2b4266ee42 | ||
| 
						 | 
					2b3c83c57e | ||
| 
						 | 
					6f37ccdee3 | ||
| 
						 | 
					df27924ac2 | ||
| 
						 | 
					3cf24cfb40 | ||
| 
						 | 
					3acf648eb4 | ||
| 
						 | 
					76cafa4249 | ||
| 
						 | 
					55943bf49a | ||
| 
						 | 
					a280d8acb2 | ||
| 
						 | 
					558a321fe8 | ||
| 
						 | 
					d901047043 | ||
| 
						 | 
					d4e0a0fa05 | ||
| 
						 | 
					22554c61c5 | ||
| 
						 | 
					72de1bbd33 | ||
| 
						 | 
					2ff912e687 | ||
| 
						 | 
					ccadb0416f | ||
| 
						 | 
					5e51beddf7 | ||
| 
						 | 
					97c9ba08d0 | ||
| 
						 | 
					39d61c66b9 | ||
| 
						 | 
					7a1eadb3fc | ||
| 
						 | 
					1bcc2f7d0c | ||
| 
						 | 
					e3cb949992 | ||
| 
						 | 
					a0c356941c | ||
| 
						 | 
					3c7868a750 | ||
| 
						 | 
					3e7d9c0411 | ||
| 
						 | 
					b21fd93d66 | ||
| 
						 | 
					37e75acd86 | ||
| 
						 | 
					6280a18c14 | ||
| 
						 | 
					5bc8b9c987 | ||
| 
						 | 
					0c570f8512 | ||
| 
						 | 
					7593afa586 | ||
| 
						 | 
					417d07f469 | ||
| 
						 | 
					b803eba934 | ||
| 
						 | 
					483207e5a0 | ||
| 
						 | 
					02ef2b2241 | ||
| 
						 | 
					13430bcad5 | ||
| 
						 | 
					e65c0e128e | ||
| 
						 | 
					bf5c040971 | ||
| 
						 | 
					5dd3d1a3b4 | ||
| 
						 | 
					6b0ea0c7bd | ||
| 
						 | 
					6bc6b3262e | ||
| 
						 | 
					3c1ae4cbd1 | ||
| 
						 | 
					547b87afc6 | ||
| 
						 | 
					db31744c98 | ||
| 
						 | 
					9423a19842 | ||
| 
						 | 
					07b303e530 | ||
| 
						 | 
					ec51148374 | ||
| 
						 | 
					0514f7805c | ||
| 
						 | 
					dfa19899b0 | ||
| 
						 | 
					1265ecab9f | ||
| 
						 | 
					1ad297ec7a | ||
| 
						 | 
					68628e3304 | ||
| 
						 | 
					1a6d7d5723 | ||
| 
						 | 
					78a8b9c58e | ||
| 
						 | 
					5e7abb66bd | ||
| 
						 | 
					358c397bb9 | ||
| 
						 | 
					3d41528059 | ||
| 
						 | 
					79637b611a | ||
| 
						 | 
					5de796b119 | ||
| 
						 | 
					bf84341acf | ||
| 
						 | 
					bbac1fdceb | ||
| 
						 | 
					c5ee1ee33c | ||
| 
						 | 
					c74eda90ed | ||
| 
						 | 
					ef2fe95bd8 | ||
| 
						 | 
					e2589b3730 | ||
| 
						 | 
					ebad1677bc | ||
| 
						 | 
					dab6ebfd82 | ||
| 
						 | 
					dd61b5360a | ||
| 
						 | 
					fced92a5a0 | ||
| 
						 | 
					64db1a654e | ||
| 
						 | 
					31830ee759 | ||
| 
						 | 
					1c05846a4f | ||
| 
						 | 
					1db9c2e420 | ||
| 
						 | 
					c966f6766c | ||
| 
						 | 
					65b6359fd8 | ||
| 
						 | 
					a54476eede | ||
| 
						 | 
					7c0354046c | ||
| 
						 | 
					43cd1c7e52 | ||
| 
						 | 
					f80c333361 | ||
| 
						 | 
					cdbb264093 | ||
| 
						 | 
					87b3bbe785 | ||
| 
						 | 
					b92cf7298a | ||
| 
						 | 
					93456b5f40 | ||
| 
						 | 
					72f29bf402 | ||
| 
						 | 
					f6fc384466 | ||
| 
						 | 
					c5ab3fdfae | ||
| 
						 | 
					78c5081a29 | ||
| 
						 | 
					d0c181ee8c | ||
| 
						 | 
					80c6bb6e8b | ||
| 
						 | 
					786b3fd3b2 | ||
| 
						 | 
					f0a22be731 | ||
| 
						 | 
					ade60022fd | ||
| 
						 | 
					7daba910ed | ||
| 
						 | 
					a016d6d91a | ||
| 
						 | 
					fcaa97ed35 | ||
| 
						 | 
					bb7373a229 | ||
| 
						 | 
					759dcc30e7 | ||
| 
						 | 
					0e47f02a33 | ||
| 
						 | 
					5fe90517e7 | ||
| 
						 | 
					92128da381 | ||
| 
						 | 
					1441fe3ae5 | ||
| 
						 | 
					b0bc71cd66 | ||
| 
						 | 
					e908e8bb34 | ||
| 
						 | 
					d708a6c6d8 | ||
| 
						 | 
					a630741098 | ||
| 
						 | 
					01d148e47c | ||
| 
						 | 
					dbd41ec439 | ||
| 
						 | 
					fc9cbab974 | ||
| 
						 | 
					36f8010ebc | ||
| 
						 | 
					e80d8fb5c8 | ||
| 
						 | 
					eabab26eef | ||
| 
						 | 
					18dbb23168 | ||
| 
						 | 
					9ad2a1e92e | ||
| 
						 | 
					5475b081b1 | ||
| 
						 | 
					ae9c295ce1 | ||
| 
						 | 
					e0ef774692 | ||
| 
						 | 
					0c1364593a | ||
| 
						 | 
					1603a4bc73 | ||
| 
						 | 
					8361cf9960 | ||
| 
						 | 
					8273e7d150 | ||
| 
						 | 
					5c49a8297f | ||
| 
						 | 
					ee1e4bf699 | ||
| 
						 | 
					f5a8e36e50 | ||
| 
						 | 
					b38a7c1da2 | ||
| 
						 | 
					fe2247329e | ||
| 
						 | 
					9d459370ce | ||
| 
						 | 
					201d96fe22 | ||
| 
						 | 
					8747429bc6 | ||
| 
						 | 
					95540efe29 | ||
| 
						 | 
					0474dc7dbe | ||
| 
						 | 
					e5af6c38e0 | ||
| 
						 | 
					00e6407347 | ||
| 
						 | 
					3805a36271 | ||
| 
						 | 
					a3cefa1c42 | ||
| 
						 | 
					81276cf2cc | ||
| 
						 | 
					541198321e | ||
| 
						 | 
					91aba39050 | ||
| 
						 | 
					f7a3da0a4d | ||
| 
						 | 
					0395792359 | ||
| 
						 | 
					2abfe4426c | ||
| 
						 | 
					3dcf3c3974 | ||
| 
						 | 
					857de65750 | ||
| 
						 | 
					f3c9f92263 | ||
| 
						 | 
					e8704e1374 | ||
| 
						 | 
					87c03b437c | ||
| 
						 | 
					1a757e8a87 | ||
| 
						 | 
					7c58f0ea96 | ||
| 
						 | 
					fcaeb381fe | ||
| 
						 | 
					5a716dff16 | ||
| 
						 | 
					8ee2716245 | ||
| 
						 | 
					5f19f0a7df | ||
| 
						 | 
					cf25621679 | ||
| 
						 | 
					228f14d06c | ||
| 
						 | 
					307c37dc44 | ||
| 
						 | 
					3b8d100f39 | ||
| 
						 | 
					fb783cdbc6 | ||
| 
						 | 
					e4d098a3ce | ||
| 
						 | 
					263f224e1b | ||
| 
						 | 
					92ca11f23c | ||
| 
						 | 
					5b3a25c461 | ||
| 
						 | 
					2431ae4d89 | ||
| 
						 | 
					2a8f0196b4 | ||
| 
						 | 
					d2791014ef | ||
| 
						 | 
					e51eb270fc | ||
| 
						 | 
					fbc98060ce | ||
| 
						 | 
					af33b6eded | ||
| 
						 | 
					51a44ce4a8 | ||
| 
						 | 
					80315f12ac | ||
| 
						 | 
					37225aec84 | ||
| 
						 | 
					39fd689f61 | ||
| 
						 | 
					5899989feb | ||
| 
						 | 
					42770989bc | ||
| 
						 | 
					eade6f3a5c | ||
| 
						 | 
					0fe334f433 | ||
| 
						 | 
					5735ddc495 | ||
| 
						 | 
					992069b22d | ||
| 
						 | 
					cbddb4b3aa | ||
| 
						 | 
					317de75a5b | ||
| 
						 | 
					9fbab27d73 | ||
| 
						 | 
					e44239b24a | ||
| 
						 | 
					debd866545 | ||
| 
						 | 
					bf34b01367 | ||
| 
						 | 
					5304cbabd9 | ||
| 
						 | 
					4de997840e | ||
| 
						 | 
					9cde39703e | ||
| 
						 | 
					ad6d2c75ca | ||
| 
						 | 
					c14176a504 | ||
| 
						 | 
					16cf97e08e | ||
| 
						 | 
					c96d37b7f1 | ||
| 
						 | 
					15a74587bc | ||
| 
						 | 
					1b3fdfbc5f | ||
| 
						 | 
					0c1b1734ee | ||
| 
						 | 
					35b9f2b764 | ||
| 
						 | 
					87618afa8d | ||
| 
						 | 
					458c8519b5 | ||
| 
						 | 
					0aa9bc2937 | ||
| 
						 | 
					a410153253 | ||
| 
						 | 
					9044dff504 | ||
| 
						 | 
					b167abcb78 | ||
| 
						 | 
					ce95593031 | ||
| 
						 | 
					30b6bc4d80 | ||
| 
						 | 
					0bebd87bd6 | ||
| 
						 | 
					f3eb46a154 | ||
| 
						 | 
					6ce2ae2391 | ||
| 
						 | 
					8cb3e8849e | ||
| 
						 | 
					07a072f8d7 | ||
| 
						 | 
					8a56c61d56 | ||
| 
						 | 
					99722e3bd1 | ||
| 
						 | 
					aa0ef4e8a4 | ||
| 
						 | 
					537bd4a7b9 | ||
| 
						 | 
					9bd41d5825 | ||
| 
						 | 
					4cc3fd3a1f | ||
| 
						 | 
					4752c3040e | ||
| 
						 | 
					61c7d6b8f9 | ||
| 
						 | 
					89b7ed4d5b | ||
| 
						 | 
					fdfdd970a7 | ||
| 
						 | 
					22bb3252cd | ||
| 
						 | 
					b4907dc1b9 | ||
| 
						 | 
					2c6436be5e | ||
| 
						 | 
					1e44ce5e5e | ||
| 
						 | 
					9de5069c20 | ||
| 
						 | 
					b0b2af8278 | 
							
								
								
									
										39
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,41 @@
 | 
				
			|||||||
node_modules/
 | 
					node_modules/
 | 
				
			||||||
tests/sandbox/
 | 
					tests/sandbox/
 | 
				
			||||||
 | 
					doc/
 | 
				
			||||||
 | 
					docs/
 | 
				
			||||||
 | 
					local/
 | 
				
			||||||
 | 
					npm-debug.log
 | 
				
			||||||
 | 
					*.map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Emacs detritus
 | 
				
			||||||
 | 
					# -*- mode: gitignore; -*-
 | 
				
			||||||
 | 
					*~
 | 
				
			||||||
 | 
					\#*\#
 | 
				
			||||||
 | 
					/.emacs.desktop
 | 
				
			||||||
 | 
					/.emacs.desktop.lock
 | 
				
			||||||
 | 
					*.elc
 | 
				
			||||||
 | 
					auto-save-list
 | 
				
			||||||
 | 
					tramp
 | 
				
			||||||
 | 
					.\#*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Org-mode
 | 
				
			||||||
 | 
					.org-id-locations
 | 
				
			||||||
 | 
					*_archive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# flymake-mode
 | 
				
			||||||
 | 
					*_flymake.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# eshell files
 | 
				
			||||||
 | 
					/eshell/history
 | 
				
			||||||
 | 
					/eshell/lastdir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# elpa packages
 | 
				
			||||||
 | 
					/elpa/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# reftex files
 | 
				
			||||||
 | 
					*.rel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# AUCTeX auto folder
 | 
				
			||||||
 | 
					/auto/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# cask packages
 | 
				
			||||||
 | 
					.cask/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.npmignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					src/
 | 
				
			||||||
 | 
					assets/
 | 
				
			||||||
 | 
					test/
 | 
				
			||||||
 | 
					doc/
 | 
				
			||||||
 | 
					.travis.yml
 | 
				
			||||||
 | 
					Gruntfile.js
 | 
				
			||||||
 | 
					.gitattributes
 | 
				
			||||||
							
								
								
									
										9
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					language: node_js
 | 
				
			||||||
 | 
					node_js:
 | 
				
			||||||
 | 
					  - "0.10"
 | 
				
			||||||
 | 
					  - "0.11"
 | 
				
			||||||
 | 
					  - "0.12"
 | 
				
			||||||
 | 
					  - "4.0"
 | 
				
			||||||
 | 
					  - "4.1"
 | 
				
			||||||
 | 
					  - "4.2"
 | 
				
			||||||
 | 
					  - "5.0"
 | 
				
			||||||
							
								
								
									
										58
									
								
								BUILDING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								BUILDING.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					Building
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*See [CONTRIBUTING.md][contrib] for more information on contributing to the
 | 
				
			||||||
 | 
					HackMyResume or FluentCV projects.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume is a standard Node.js command line app implemented in a mix of
 | 
				
			||||||
 | 
					CoffeeScript and JavaScript. Setting up a build environment is easy:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Prerequisites ##
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. OS: Linux, OS X, or Windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Install [Node.js][node] and [Grunt][grunt].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Set up a build environment ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Fork [hacksalot/HackMyResume][hmr] to your GitHub account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Clone your fork locally.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. From within the top-level HackMyResume folder, run `npm install` to install
 | 
				
			||||||
 | 
					project dependencies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. Create a new branch, based on the latest HackMyResume `dev` branch, to
 | 
				
			||||||
 | 
					contain your work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. Run `npm link` in the HackMyResume folder so that the `hackmyresume` command
 | 
				
			||||||
 | 
					will reference your local installation (you may need to
 | 
				
			||||||
 | 
					`npm uninstall -g hackmyresume` first).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Making changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. HackMyResume sources live in the [`/src`][src] folder. Always make your edits
 | 
				
			||||||
 | 
					there, never in the generated `/dist` folder.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. After making your changes, run `grunt build` to package the HackMyResume
 | 
				
			||||||
 | 
					sources to the `/dist` folder. This will transform CoffeeScript files to
 | 
				
			||||||
 | 
					JavaScript and perform other build steps as necessary. In the future, a watch
 | 
				
			||||||
 | 
					task or guardfile will be added to automate this step.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Do local spot testing with `hackmyresume` as normal.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. When you're ready to submit your changes, run `grunt test` to run the HMR
 | 
				
			||||||
 | 
					test suite. Fix any errors that occur.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. Commit and push your changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6. Submit a pull request targeting the HackMyResume `dev` branch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[node]: https://nodejs.org/en/
 | 
				
			||||||
 | 
					[grunt]: http://gruntjs.com/
 | 
				
			||||||
 | 
					[hmr]: https://github.com/hacksalot/HackMyResume
 | 
				
			||||||
 | 
					[src]: https://github.com/hacksalot/HackMyResume/tree/master/src
 | 
				
			||||||
 | 
					[contrib]: https://github.com/hacksalot/HackMyResume/blob/master/CONTRIBUTING.md
 | 
				
			||||||
							
								
								
									
										411
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,411 @@
 | 
				
			|||||||
 | 
					CHANGELOG
 | 
				
			||||||
 | 
					=========
 | 
				
			||||||
 | 
					## v1.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Updated `Awesome` theme to latest version of [Awesome-CV][acv].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Introduced new theme helpers: `pad`, `date`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed an issue where the `Awesome` theme wouldn't correctly generate LaTeX
 | 
				
			||||||
 | 
					outputs (#138).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Emit a line number for syntax errors around embedded newlines in JSON strings
 | 
				
			||||||
 | 
					(#137).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix several PDF / PNG generation errors (#132, others).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Display a more helpful error message when attempting to generate a PDF or PNG
 | 
				
			||||||
 | 
					on a machine where PhantomJS and/or wkhtmltopdf are either not installed or
 | 
				
			||||||
 | 
					not path-accessible.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed an issue that would cause long-running PDF/PNG generation to fail in
 | 
				
			||||||
 | 
					certain environments.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed an issue involving an unhelpful spawn-related exception (#136).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Internal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- JSHint will no longer gripe at the use of `== null` and `!= null` in
 | 
				
			||||||
 | 
					CoffeeScript transpilation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Introduced [template-friendly Awesome-CV fork][awefork] to isolate template
 | 
				
			||||||
 | 
					expansion logic & provide better durability for HackMyResume's `awesome` theme.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed a couple temporary regressions (#139, #140) on the dev branch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Additional tests.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Minor breaking HackMyResume API changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.7.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Build instructions](https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- More precise date handling.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Issue with incomplete PDF generation (#127).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Issue with building JSON Resume themes (#128).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Issue with generating `.json` output format by itself (#97).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.7.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Issue with generated PDFs being chopped off and displaying a mysterious sequence of numbers of unknown and possibly alien origin (#127).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Unsightly border on Modern:PDF.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Modern|Positive:PDF formats now correctly reference their PDF-specific CSS files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `Incorrect helper use` warning in Positive:DOC.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.7.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Interim release supporting FluentCV Desktop.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Internal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Moved [HackMyCore](https://github.com/hacksalot/HackMyCore) dependency to
 | 
				
			||||||
 | 
					submodule.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.7.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Caffeinate. CoffeeScript now used throughout
 | 
				
			||||||
 | 
					[HackMyResume](https://github.com/hacksalot/HackMyResume) and
 | 
				
			||||||
 | 
					[HackMyCore](https://github.com/hacksalot/HackMyCore); generated JavaScript
 | 
				
			||||||
 | 
					lives in `/dist`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Issue with generating a single PDF with the `.pdf` extension (#99).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.7.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Internal] Relocated HMR processing code to the
 | 
				
			||||||
 | 
					[HackMyCore](https://github.com/hacksalot/HackMyCore) project. Shouldn't affect
 | 
				
			||||||
 | 
					normal use.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.6.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Major Improvements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Better consistency and coverage for all FRESH resumes and themes ([#45][i45]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Initial support for overridable fonts in FRESH themes. Like a particular
 | 
				
			||||||
 | 
					theme, but want to change the typography? The specific fonts used by a theme
 | 
				
			||||||
 | 
					can now be overridden by the user. (FRESH themes only).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- New resume sections! Support for `projects` and `affiliation` resume sections
 | 
				
			||||||
 | 
					for technical and creative projects and memberships / clubs / associations,
 | 
				
			||||||
 | 
					respectively ([#92][i92]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- New command! `PEEK` at any arbitrary field or entry on your `.json` resume.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Improved handling of start and end dates on `employment`, `projects`,
 | 
				
			||||||
 | 
					`education`, and other sections with start/end dates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Support for an `.ignore` property on any FRESH or JSON Resume section or field.
 | 
				
			||||||
 | 
					Ignored properties will be treated by HackMyResume as if they weren't present.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Emit extended status and error info with the `--debug` or `-d` switch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- The `-o` or `--options` switch can now handle either the path to a **JSON
 | 
				
			||||||
 | 
					settings file** or **raw JSON/JavaScript**. Since the JSON double quote syntax
 | 
				
			||||||
 | 
					is a bit cumbersome from the command line, HackMyResume accepts regular
 | 
				
			||||||
 | 
					JavaScript object literal syntax:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hackmyresume build resume.json -o "{ theme: 'compact', silent: 'true' }"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Ability to disable sorting of resume sections (employments, projects, etc.)
 | 
				
			||||||
 | 
					with the `--no-sort` option. HMR will respect the order of items as they appear
 | 
				
			||||||
 | 
					in your resume `.json` file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Improvements to the starter resume emitted by `hackmyresume new`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Theme Authoring: Annotated the HTML and MS Word (XML) formats of the Modern
 | 
				
			||||||
 | 
					theme for FRESH theme authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Theme Authoring: Support for templatized CSS files in FRESH themes. CSS files
 | 
				
			||||||
 | 
					are now expanded via Handlebars or Underscore prior to copying to the
 | 
				
			||||||
 | 
					destination.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added CHANGELOG.md (this file).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Rewrote the HackMyResume man/help page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Minor incremental updates to the [FRESCA][fresca] schema.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- PDF generation now uses asynchronous `spawn()` which has better compatibility
 | 
				
			||||||
 | 
					with old or boutique versions of Node.js.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Refactored colors in HackMyResume output. Errors will now display as red,
 | 
				
			||||||
 | 
					warnings as yellow, successful operations as green, and informational messages
 | 
				
			||||||
 | 
					as cyan.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Theme messages and usage tips will no longer display during resume generation
 | 
				
			||||||
 | 
					by default. Use the `--tips` option to view them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- The `--no-tips` option (default: false) has been replaced with the `--tips`
 | 
				
			||||||
 | 
					option, also defaulting to false.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Removed the `hello-world` theme from the [prebuilt themes][themes] that ship
 | 
				
			||||||
 | 
					with HackMyResume. It can be installed separately from NPM:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ```bash
 | 
				
			||||||
 | 
					  npm install fresh-theme-hello-world
 | 
				
			||||||
 | 
					  hackmyresume resume.json -t node_modules/fresh-theme-hello-world
 | 
				
			||||||
 | 
					  ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- sd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- PDF generation issues on older versions of Node.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Stack traces not being emitted correctly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Missing `speaking` section will now appear on generated resumes ([#101][i101]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Incomplete `education` details will now appear on generated resumes ([#65][i65]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Missing employment end date being interpreted as "employment ends today"
 | 
				
			||||||
 | 
					([#84][i84]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Merging multiple source resumes during `BUILD` sometimes fails.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Document `--pdf` flag in README ([#111][i111]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Internal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Logging messages have been moved out of core HackMyResume code ahead of
 | 
				
			||||||
 | 
					localization support.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- All HackMyResume console output is described in `msg.yml`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Relaxed pure JavaScript requirement. CoffeeScript will now start appearing
 | 
				
			||||||
 | 
					in HackMyResume and FluentCV sources!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Additional tests.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.5.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Tweak stack trace under `--debug`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.5.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Preliminary support for `-d` or `--debug` flag. Forces HackMyResume to emit a stack trace under error conditions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.5.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- HackMyResume now supports **CLI-based generation of PDF formats across multiple engines (Phantom, wkhtmltopdf, etc)**. Instead of talking to these engines over a programmatic API, as in prior versions, HackMyResume 1.5+ speaks to them over the same command-line interface (CLI) you'd use if you were using these tools directly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- HackMyResume will now (attempt to) **generate a PDF output for JSON Resume themes** (in addition to HTML).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Minor README and FAQ additions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Cleaner, quicker installs**. Installing HackMyResume with `npm install hackmyresume -g` will no longer trigger a lengthy, potentially error-prone install for Phantom.js and/or wkhtmltopdf for PDF support. Instead, users can install these engines externally and HMR will use them when present.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Minor error handling improvements.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed an error with generating specific formats with the `BUILD` command (#97).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed numerous latent/undocumented bugs and glitches.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.4.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Introduced [FAQ](https://github.com/hacksalot/HackMyResume/blob/master/FAQ.md).
 | 
				
			||||||
 | 
					- Additional README notes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.4.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `hackmyresume new` now generates a [valid starter resume with sample data](https://github.com/fluentdesk/fresh-resume-starter).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed warning message when `hackmyresume new` is run without a filename.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.4.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **"Projects" support**: FRESH resumes and themes can now store and display
 | 
				
			||||||
 | 
					open source, commercial, private, personal, and creative projects.
 | 
				
			||||||
 | 
					- **New command: ANALYZE**. Inspect your resume for gaps, keyword counts, and other metrics. (Experimental.)
 | 
				
			||||||
 | 
					- **Side-by-side PDF generation** with Phantom and wkhtmltopdf. Use the `--pdf` or `-p` flag to pick between `phantom` and `wkhtmltopdf` generation.
 | 
				
			||||||
 | 
					- **Disable PDF generation** with the `--pdf none` switch.
 | 
				
			||||||
 | 
					- **Inherit formats between themes**. Themes can now inherit formats (Word, HTML, .txt, etc.) from other themes. (FRESH themes only.)
 | 
				
			||||||
 | 
					- **Rename resume sections** to different languages or wordings.
 | 
				
			||||||
 | 
					- **Specify complex options via external file**. Use with the `-o` or `--opts` option.
 | 
				
			||||||
 | 
					- **Disable colors** with the `--no-color` flag.
 | 
				
			||||||
 | 
					- **Theme messages and usage tips** instructions will now appear in the default HackMyResume output for the `build` command. Run `hackmyresume build resume.json -t awesome` for an example. Turn off with the `--no-tips` flag.
 | 
				
			||||||
 | 
					- **Treat validation errors as warnings** with the `--assert` switch (VALIDATE command only).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed a minor glitch in the FRESCA schema.
 | 
				
			||||||
 | 
					- Fixed encoding issues in the `Highlights` section of certain resumes.
 | 
				
			||||||
 | 
					- Fix behavior of `-s` and `--silent` flags.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- PDF generation now defaults to Phantom for all platforms, with `wkhtmltopdf`
 | 
				
			||||||
 | 
					accessible with `--pdf wkhtmltopdf`.
 | 
				
			||||||
 | 
					- Resumes are now validated, by default, prior to generation. This
 | 
				
			||||||
 | 
					behavior can be disabled with the `--novalidate` or `--force` switch.
 | 
				
			||||||
 | 
					- Syntax errors in source FRESH and JSON Resumes are now captured for all
 | 
				
			||||||
 | 
					commands.
 | 
				
			||||||
 | 
					- Minor updates to README.
 | 
				
			||||||
 | 
					- Most themes now inherit Markdown and Plain Text formats from the **Basis**
 | 
				
			||||||
 | 
					theme.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Internal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Switched from color to chalk.
 | 
				
			||||||
 | 
					- Command invocations now handled through commander.js.
 | 
				
			||||||
 | 
					- Improved FRESH theme infrastructure (more partials, more DRY).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.3.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add additional Travis badges.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix extraneous console log output when generating a FRESH theme to MS Word.
 | 
				
			||||||
 | 
					- Fix Travis tests on `dev`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.3.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Local generation of JSON Resume themes**. To use a JSON Resume theme, first install it with `npm install jsonresume-theme-[blah]` (most JSON Resume themes are on NPM). Then pass it into HackMyResume via the `-t` parameter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    `hackmyresume BUILD resume.json TO out/somefile.all -t node_modules/jsonresume-theme-classy`
 | 
				
			||||||
 | 
					- **Better Markdown support.** HackMyResume will start flowing basic Markdown styles to JSON Resume (HTML) themes. FRESH's existing Markdown support has also been improved.
 | 
				
			||||||
 | 
					- **.PNG output formats** will start appearing in themes that declare an HTML output.
 | 
				
			||||||
 | 
					- **Tweak CSS embedding / linking via the --css option** (`<style></style>` vs `<link>`). Only works for HTML (or HTML-driven) formats of FRESH themes. Use `--css=link` to link in CSS assets and `--css=embed` to embed the styles in the HTML document. For example `hackmyresume BUILD resume.json TO out/resume.all --css=link`.
 | 
				
			||||||
 | 
					- **Improved Handlebars/Underscore helper support** for FRESH themes. Handlebars themes can access helpers via `{{helperName}}`. Underscore themes can access helpers via the `h` object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Distinguish between validation errors and syntax errors** when validating a FRESH or JRS resume with `hackmyresume validate <blah>`.
 | 
				
			||||||
 | 
					- **Emit line and column info** for syntax errors during validation of FRESH and JRS resumes.
 | 
				
			||||||
 | 
					- **FRESH themes now embed CSS into HTML formats by default** so that the HTML resume output doesn't have an external CSS file dependency by default. Users can specify normal linked stylesheets by setting `--css=link`.
 | 
				
			||||||
 | 
					- **Renamed fluent-themes repo to fresh-themes** in keeping with the other parts of the project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix various encoding errors in MS Word outputs.
 | 
				
			||||||
 | 
					- Fix assorted FRESH-to-JRS and JRS-to-FRESH conversion glitches.
 | 
				
			||||||
 | 
					- Fix error when running HMR with no parameters.
 | 
				
			||||||
 | 
					- Other minor fixes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.3.0-beta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Numerous changes supporting v1.3.0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.2.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Various in-passing fixes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.2.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix `require('FRESCA')` error.
 | 
				
			||||||
 | 
					- Fix `.history` and `.map` errors on loading incomplete or empty JRS resumes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Better test coverage of incomplete/empty resumes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed the `new` command: Generate a new FRESH-format resume with `hackmyresume new resume.json` or a new JSON Resume with `hackmyresume new resume.json -f jrs`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Introduced CLI tests.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- MS Word formats: Fixed skill coloring/level bug.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Make the `TO` keyword optional. If no `TO` keyword is specified (for the `build` and `convert` commands), HMR will assume the last file passed in is the desired output file. So these are equivalent:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```shell
 | 
				
			||||||
 | 
					    hackmyresume BUILD resume.json TO out/resume.all
 | 
				
			||||||
 | 
					    hackmyresume BUILD resume.json out/resume.all
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    `TO` only needs to be included if you have multipled output files:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```shell
 | 
				
			||||||
 | 
					     hackmyresume BUILD resume.json TO out1.doc out2.html out3.tex
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Correctly generate MS Word hyperlinks from Markdown source data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Initial public 1.0 release.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[i45]: https://github.com/hacksalot/HackMyResume/issues/45
 | 
				
			||||||
 | 
					[i65]: https://github.com/hacksalot/HackMyResume/issues/65
 | 
				
			||||||
 | 
					[i84]: https://github.com/hacksalot/HackMyResume/issues/84
 | 
				
			||||||
 | 
					[i92]: https://github.com/hacksalot/HackMyResume/issues/92
 | 
				
			||||||
 | 
					[i101]: https://github.com/hacksalot/HackMyResume/issues/101
 | 
				
			||||||
 | 
					[i111]: https://github.com/hacksalot/HackMyResume/issues/111
 | 
				
			||||||
 | 
					[fresca]: https://github.com/fluentdesk/FRESCA
 | 
				
			||||||
 | 
					[themes]: https://github.com/fluentdesk/fresh-themes
 | 
				
			||||||
 | 
					[awefork]: https://github.com/fluentdesk/Awesome-CV
 | 
				
			||||||
 | 
					[acv]: https://github.com/posquit0/Awesome-CV
 | 
				
			||||||
							
								
								
									
										53
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					Contributing
 | 
				
			||||||
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Note: HackMyResume is also available as [FluentCV][fcv]. Contributors are
 | 
				
			||||||
 | 
					credited in both.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How To Contribute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*See [BUILDING.md][building] for instructions on setting up a HackMyResume
 | 
				
			||||||
 | 
					development environment.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Optional: [**open an issue**][iss] identifying the feature or bug you'd like
 | 
				
			||||||
 | 
					to implement or fix. This step isn't required — you can start hacking away on
 | 
				
			||||||
 | 
					HackMyResume without clearing it with us — but helps avoid duplication of work
 | 
				
			||||||
 | 
					and ensures that your changes will be accepted once submitted.
 | 
				
			||||||
 | 
					2. **Fork and clone** the HackMyResume project.
 | 
				
			||||||
 | 
					3. Ideally, **create a new feature branch** (eg, `feat/new-awesome-feature` or
 | 
				
			||||||
 | 
					similar; call it whatever you like) to perform your work in.
 | 
				
			||||||
 | 
					4. **Install dependencies** by running `npm install` in the top-level
 | 
				
			||||||
 | 
					HackMyResume folder.
 | 
				
			||||||
 | 
					5. Make your **commits** as usual.
 | 
				
			||||||
 | 
					6. **Verify** your changes locally with `grunt test`.
 | 
				
			||||||
 | 
					7. **Push** your commits.
 | 
				
			||||||
 | 
					7. **Submit a pull request** from your feature branch to the HackMyResume `dev`
 | 
				
			||||||
 | 
					branch.
 | 
				
			||||||
 | 
					8. We'll typically **respond** within 24 hours.
 | 
				
			||||||
 | 
					9. Your awesome changes will be **merged** after verification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Project Maintainers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume is currently maintained by [hacksalot][ha] with assistance from
 | 
				
			||||||
 | 
					[tomheon][th] and our awesome [contributors][awesome]. Please direct all official
 | 
				
			||||||
 | 
					or internal inquiries to:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					admin@hackmyresume.com
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can reach hacksalot directly at:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					hacksalot@indevious.com
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thanks for your interest in the HackMyResume project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[fcv]: https://github.com/fluentdesk/fluentcv
 | 
				
			||||||
 | 
					[flow]: https://guides.github.com/introduction/flow/
 | 
				
			||||||
 | 
					[iss]: https://github.com/hacksalot/HackMyResume/issues
 | 
				
			||||||
 | 
					[ha]: https://github.com/hacksalot
 | 
				
			||||||
 | 
					[th]: https://github.com/tomheon
 | 
				
			||||||
 | 
					[awesome]: https://github.com/hacksalot/HackMyResume/graphs/contributors
 | 
				
			||||||
 | 
					[building]: https://github.com/hacksalot/HackMyResume/blob/master/BUILDING.md
 | 
				
			||||||
							
								
								
									
										228
									
								
								FAQ.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								FAQ.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					Frequently Asked Questions (FAQ)
 | 
				
			||||||
 | 
					================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How do I get started with HackMyResume?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Install with NPM: `[sudo] npm install hackmyresume -g`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Create a new resume with: `hackmyresume NEW <resume-name>.json`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Test with `hackmyresume BUILD <resume-name>.json`. Look in the `out/` folder.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. Play around with different themes with the `-t` or `--theme` parameter.
 | 
				
			||||||
 | 
					You can use any [FRESH](https://github.com/fluentdesk/fresh-themes) or
 | 
				
			||||||
 | 
					[JSON Resume](https://jsonresume.org/themes) theme. The latter have to be
 | 
				
			||||||
 | 
					installed first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What is FRESH?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FRESH is the **F**luent **R**esume and **E**mployment **S**ystem for **H**umans.
 | 
				
			||||||
 | 
					It's an open-source, user-first workflow, schema, and set of practices for
 | 
				
			||||||
 | 
					technical candidates and recruiters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What is FRESCA?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The **F**RESH **R**esume and **E**mployment **SC**hem**A**—an open-source,
 | 
				
			||||||
 | 
					JSON-driven schema for resumes, CVs, and other employment artifacts. FRESCA is
 | 
				
			||||||
 | 
					the recommended schema/format for FRESH, with optional support for JSON Resume.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What is JSON Resume?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An [open resume standard](http://jsonresume.org/themes/) sponsored by Hired.com.
 | 
				
			||||||
 | 
					Like FRESCA, JSON Resume is JSON-driven and open-source. Unlike FRESCA, JSON
 | 
				
			||||||
 | 
					Resume targets a worldwide audience where FRESCA is optimized for technical
 | 
				
			||||||
 | 
					candidates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Should I use the FRESH or JSON Resume format/schema for my resume?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Both! The workflow we like to use:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Create a resume in FRESH format for tooling and analysis.
 | 
				
			||||||
 | 
					2. Convert it to JSON Resume format for additional themes/tools.
 | 
				
			||||||
 | 
					3. Maintain both versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Both formats are open-source and both formats are JSON-driven. FRESH was
 | 
				
			||||||
 | 
					designed as a universal container format and superset of existing formats, where
 | 
				
			||||||
 | 
					the JSON Resume format is intended for a generic audience.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How do I use a FRESH theme?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Several FRESH themes come preinstalled with HackMyResume; others can be
 | 
				
			||||||
 | 
					installed from NPM and GitHub.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### To use a preinstalled FRESH theme:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Pass the theme name into HackMyResume via the `--theme` or `-t` parameter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    hackmyresume build resume.json --theme compact
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### To use an external FRESH theme:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Install the theme locally. The easiest way to do that is with NPM.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    npm install fresh-theme-underscore
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Pass the theme folder into HackMyResume:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    hackmyresume BUILD resume.json --theme node_modules/fresh-theme-underscore
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Check your output folder. It's best to view HTML formats over a local web
 | 
				
			||||||
 | 
					server connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How do I use a JSON Resume theme?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					JSON Resume (JRS) themes can be installed from NPM and GitHub and passed into
 | 
				
			||||||
 | 
					HackMyResume via the `--theme` or `-t` parameter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Install the theme locally. The easiest way to do that is with NPM.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    npm install jsonresume-theme-classy
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Pass the theme folder path into HackMyResume:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    hackmyresume BUILD resume.json --theme node_modules/jsonresume-theme-classy
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Check your output folder. It's best to view HTML formats over a local web
 | 
				
			||||||
 | 
					server connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Should I keep my resume in version control?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Absolutely! As text-based, JSON-driven documents, both FRESH and JSON Resume are
 | 
				
			||||||
 | 
					ideal candidates for version control. Future versions of HackMyResume will have
 | 
				
			||||||
 | 
					this functionality built in.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Can I change the default section titles ("Employment", "Skills", etc.)?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you're using a FRESH theme, yes. First, create a HackMyResume options file
 | 
				
			||||||
 | 
					mapping resume sections to your preferred section title:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					// myoptions.json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "sectionTitles": {
 | 
				
			||||||
 | 
					    "employment": "empleo",
 | 
				
			||||||
 | 
					    "skills": "habilidades",
 | 
				
			||||||
 | 
					    "education": "educación"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then, pass the options file into the `-o` or `--opts` parameter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json -o myoptions.json
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This ability is currently only supported for FRESH resume themes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How does resume merging work?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Resume merging is a way of storing your resume in separate files that
 | 
				
			||||||
 | 
					HackMyResume will merge into a single "master" resume file prior to generating
 | 
				
			||||||
 | 
					specific output formats like HTML or PDF. It's a way of producing flexible,
 | 
				
			||||||
 | 
					configurable, targeted resumes with minimal duplication.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For example, a software developer who moonlights as a game programmer might
 | 
				
			||||||
 | 
					create three FRESH or JRS resumes at different levels of specificity:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **generic.json**: A generic technical resume, suitable for all audiences.
 | 
				
			||||||
 | 
					- **game-developer.json**: Overrides and amendments for game developer
 | 
				
			||||||
 | 
					positions.
 | 
				
			||||||
 | 
					- **blizzard.json**: Overrides and amendments specific to a hypothetical
 | 
				
			||||||
 | 
					position at Blizzard.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you run `hackmyresume BUILD generic.json TO out/resume.all`, HMR will
 | 
				
			||||||
 | 
					generate all available output formats for the `generic.json` as usual. But if
 | 
				
			||||||
 | 
					you instead run...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD generic.json game-developer.json TO out/resume.all
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					...HackMyResume will notice that multiple source resumes were specified and
 | 
				
			||||||
 | 
					merge `game-developer.json` onto `generic.json` before generating, yielding a
 | 
				
			||||||
 | 
					resume that's more suitable for game-developer-related positions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can take this a step further. Let's say you want to do a targeted resume
 | 
				
			||||||
 | 
					submission to a game developer position at Blizzard, and `blizzard.json`
 | 
				
			||||||
 | 
					contains the edits and revisions you'd like to show up in the targeted resume.
 | 
				
			||||||
 | 
					In that case, merge again! Feed all three resumes to HackMyResume, in order
 | 
				
			||||||
 | 
					from most generic to most specific, and HMR will merge them all prior to
 | 
				
			||||||
 | 
					generating the final output format(s) for your resume.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Merge blizzard.json onto game-developer.json onto generic.json, then build
 | 
				
			||||||
 | 
					hackmyresume BUILD generic.json game-developer.json blizzard.json TO out/resume.all
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There's no limit to the number of resumes you can merge this way.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can also divide your resume into files containing different sections:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **resume-a.json**: Contains `info`, `employment`, and `summary` sections.
 | 
				
			||||||
 | 
					- **resume-b.json**: Contains all other sections except `references`.
 | 
				
			||||||
 | 
					- **references.json**: Contains the private `references` section.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Under that scenario, `hackmyresume BUILD resume-a.json resume-b.json` would
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## The HackMyResume terminal color scheme is giving me a headache. Can I disable it?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yes. Use the `--no-color` option to disable terminal colors:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`hackmyresume <somecommand> <someoptions> --no-color`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What's the difference between a FRESH theme and a JSON Resume theme?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FRESH themes are multiformat (HTML, Word, PDF, etc.) and required to support
 | 
				
			||||||
 | 
					Markdown formatting, configurable section titles, and various other features.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					JSON Resume themes are typically HTML-driven, but capable of expansion to other
 | 
				
			||||||
 | 
					formats through tools. JSON Resume themes don't support Markdown natively, but
 | 
				
			||||||
 | 
					HMR does its best to apply your Markdown, when present, to any JSON Resume
 | 
				
			||||||
 | 
					themes it encounters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Do I have to have a FRESH resume to use a FRESH theme or a JSON Resume to use a JSON Resume theme?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					No. You can mix and match FRESH and JRS-format themes freely. HackMyResume will
 | 
				
			||||||
 | 
					perform the necessary conversions on the fly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Can I build my own custom FRESH theme?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yes. The easiest way is to copy an existing FRESH theme, like `modern` or
 | 
				
			||||||
 | 
					`compact`, and make your changes there. You can test your theme with:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume build resume.json --theme path/to/my/theme/folder
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Can I build my own custom JSON Resume theme?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yes. The easiest way is to copy an existing JSON Rsume theme and make your
 | 
				
			||||||
 | 
					changes there. You can test your theme with:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume build resume.json --theme path/to/my/theme/folder
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Can I build my own tools / services / apps / websites around FRESH / FRESCA?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yes! FRESH/FRESCA formats are 100% open source, permissively licensed under MIT,
 | 
				
			||||||
 | 
					and 100% free from company-specific, tool-specific, or commercially oriented
 | 
				
			||||||
 | 
					lock-in or cruft. These are clean formats designed for users and builders.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Can I build my own tools / services / apps / websites around JSON Resume?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yes! HackMyResume is not affiliated with JSON Resume, but like FRESH/FRESCA,
 | 
				
			||||||
 | 
					JSON Resume is open-source, permissively licensed, and free of proprietary
 | 
				
			||||||
 | 
					lock-in. See the JSON Resume website for details.
 | 
				
			||||||
							
								
								
									
										96
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								Gruntfile.js
									
									
									
									
									
								
							@@ -1,11 +1,33 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = function (grunt) {
 | 
					module.exports = function (grunt) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var opts = {
 | 
					  var opts = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pkg: grunt.file.readJSON('package.json'),
 | 
					    pkg: grunt.file.readJSON('package.json'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    copy: {
 | 
				
			||||||
 | 
					      main: {
 | 
				
			||||||
 | 
					        expand: true,
 | 
				
			||||||
 | 
					        cwd: 'src',
 | 
				
			||||||
 | 
					        src: ['**/*','!**/*.coffee'],
 | 
				
			||||||
 | 
					        dest: 'dist/',
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    coffee: {
 | 
				
			||||||
 | 
					      main: {
 | 
				
			||||||
 | 
					        options: {
 | 
				
			||||||
 | 
					          sourceMap: true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        expand: true,
 | 
				
			||||||
 | 
					        cwd: 'src',
 | 
				
			||||||
 | 
					        src: ['**/*.coffee'],
 | 
				
			||||||
 | 
					        dest: 'dist/',
 | 
				
			||||||
 | 
					        ext: '.js'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    simplemocha: {
 | 
					    simplemocha: {
 | 
				
			||||||
      options: {
 | 
					      options: {
 | 
				
			||||||
        globals: ['expect', 'should'],
 | 
					        globals: ['expect', 'should'],
 | 
				
			||||||
@@ -14,16 +36,76 @@ module.exports = function (grunt) {
 | 
				
			|||||||
        ui: 'bdd',
 | 
					        ui: 'bdd',
 | 
				
			||||||
        reporter: 'spec'
 | 
					        reporter: 'spec'
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      all: { src: ['tests/*.js'] }
 | 
					      all: { src: ['test/*.js'] }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    jsdoc : {
 | 
				
			||||||
 | 
					      dist : {
 | 
				
			||||||
 | 
					        src: ['src/**/*.js'],
 | 
				
			||||||
 | 
					        options: {
 | 
				
			||||||
 | 
					          private: true,
 | 
				
			||||||
 | 
					          destination: 'doc'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clean: {
 | 
				
			||||||
 | 
					      test: ['test/sandbox'],
 | 
				
			||||||
 | 
					      dist: ['dist']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yuidoc: {
 | 
				
			||||||
 | 
					      compile: {
 | 
				
			||||||
 | 
					        name: '<%= pkg.name %>',
 | 
				
			||||||
 | 
					        description: '<%= pkg.description %>',
 | 
				
			||||||
 | 
					        version: '<%= pkg.version %>',
 | 
				
			||||||
 | 
					        url: '<%= pkg.homepage %>',
 | 
				
			||||||
 | 
					        options: {
 | 
				
			||||||
 | 
					          paths: 'src/',
 | 
				
			||||||
 | 
					          outdir: 'docs/'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    jshint: {
 | 
				
			||||||
 | 
					      options: {
 | 
				
			||||||
 | 
					        laxcomma: true,
 | 
				
			||||||
 | 
					        expr: true,
 | 
				
			||||||
 | 
					        eqnull: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      all: ['Gruntfile.js', 'dist/cli/**/*.js', 'test/*.js']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.initConfig( opts );
 | 
					  grunt.initConfig( opts );
 | 
				
			||||||
 | 
					  grunt.loadNpmTasks('grunt-contrib-coffee');
 | 
				
			||||||
 | 
					  grunt.loadNpmTasks('grunt-contrib-copy');
 | 
				
			||||||
  grunt.loadNpmTasks('grunt-simple-mocha');
 | 
					  grunt.loadNpmTasks('grunt-simple-mocha');
 | 
				
			||||||
  grunt.registerTask('test', 'Test the FluentLib library.', function( config ) {
 | 
					  grunt.loadNpmTasks('grunt-contrib-yuidoc');
 | 
				
			||||||
    grunt.task.run( ['simplemocha:all'] );
 | 
					  grunt.loadNpmTasks('grunt-jsdoc');
 | 
				
			||||||
  });
 | 
					  grunt.loadNpmTasks('grunt-contrib-jshint');
 | 
				
			||||||
  grunt.registerTask('default', [ 'test' ]);
 | 
					  grunt.loadNpmTasks('grunt-contrib-clean');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Use 'grunt test' for local testing
 | 
				
			||||||
 | 
					  grunt.registerTask('test', 'Test the HackMyResume application.',
 | 
				
			||||||
 | 
					    function( config ) {
 | 
				
			||||||
 | 
					      grunt.task.run(['clean:test','build','jshint','simplemocha:all']);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Use 'grunt document' to build docs
 | 
				
			||||||
 | 
					  grunt.registerTask('document', 'Generate HackMyResume documentation.',
 | 
				
			||||||
 | 
					    function( config ) {
 | 
				
			||||||
 | 
					      grunt.task.run( ['jsdoc'] );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Use 'grunt build' to build HMR
 | 
				
			||||||
 | 
					  grunt.registerTask('build', 'Build the HackMyResume application.',
 | 
				
			||||||
 | 
					    function( config ) {
 | 
				
			||||||
 | 
					      grunt.task.run( ['clean:dist','copy','coffee'] );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Default task does everything
 | 
				
			||||||
 | 
					  grunt.registerTask('default', [ 'test', 'document' ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
The MIT License
 | 
					The MIT License
 | 
				
			||||||
===============
 | 
					===============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Copyright (c) 2015 James M. Devlin (https://github.com/devlinjd)
 | 
					Copyright (c) 2015-2016 hacksalot (https://github.com/hacksalot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										513
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										513
									
								
								README.md
									
									
									
									
									
								
							@@ -1,64 +1,217 @@
 | 
				
			|||||||
fluentCV
 | 
					HackMyResume
 | 
				
			||||||
========
 | 
					============
 | 
				
			||||||
*Generate beautiful, targeted resumes from your command line or shell.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
FluentCV is a **hackable, data-driven, dev-friendly resume authoring tool** with support for HTML, Markdown, Word, PDF, plain text, smoke signal, carrier pigeon, and other arbitrary-format resumes and CVs.
 | 
					[![Latest release][img-release]][latest-release]
 | 
				
			||||||
 | 
					[![Build status (MASTER)][img-master]][travis-url-master]
 | 
				
			||||||
 | 
					[![Build status (DEV)][img-dev]][travis-url-dev]
 | 
				
			||||||
 | 
					[](https://gitter.im/hacksalot/HackMyResume?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					*Create polished résumés and CVs in multiple formats from your command line or
 | 
				
			||||||
 | 
					shell. Author in clean Markdown and JSON, export to Word, HTML, PDF, LaTeX,
 | 
				
			||||||
 | 
					plain text, and other arbitrary formats. Fight the power, save trees. Compatible
 | 
				
			||||||
 | 
					with [FRESH][fresca] and [JRS][6] resumes.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Looking for a desktop GUI version with pretty timelines and graphs? Check out [FluentCV Desktop][7].
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume is a dev-friendly, local-only Swiss Army knife for resumes and CVs.
 | 
				
			||||||
 | 
					Use it to:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **Generate** HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML,
 | 
				
			||||||
 | 
					YAML, print, smoke signal, carrier pigeon, and other arbitrary-format resumes
 | 
				
			||||||
 | 
					and CVs, from a single source of truth—without violating DRY.
 | 
				
			||||||
 | 
					2. **Analyze** your resume for keyword density, gaps/overlaps, and other
 | 
				
			||||||
 | 
					metrics.
 | 
				
			||||||
 | 
					3. **Convert** resumes between [FRESH][fresca] and [JSON Resume][6] formats.
 | 
				
			||||||
 | 
					4. **Validate** resumes against either format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume is built with Node.js and runs on recent versions of OS X, Linux,
 | 
				
			||||||
 | 
					or Windows. View the [FAQ](FAQ.md).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Runs on OS X, Linux, and Windows.
 | 
					- OS X, Linux, and Windows.
 | 
				
			||||||
- Store your resume data as a durable, versionable JSON, YML, or XML document.
 | 
					- Choose from dozens of FRESH or JSON Resume themes.
 | 
				
			||||||
- Generate multiple targeted resumes in multiple formats, based on your needs.
 | 
					- Private, local-only resume authoring and analysis.
 | 
				
			||||||
- Output to HTML, PDF, Markdown, Word, JSON, YAML, XML, or a custom format.
 | 
					- Analyze your resume for keywords, gaps, and other metrics.
 | 
				
			||||||
- Never update one piece of information in four different resumes again.
 | 
					- Store your resume data as a durable, versionable JSON or YAML document.
 | 
				
			||||||
- Compatible with the [JSON Resume standard][6] and [authoring tools][7].
 | 
					- Generate polished resumes in multiple formats without violating [DRY][dry].
 | 
				
			||||||
 | 
					- Output to HTML, Markdown, LaTeX, PDF, MS Word, JSON, YAML, plain text, or XML.
 | 
				
			||||||
 | 
					- Validate resumes against the FRESH or JSON Resume schema.
 | 
				
			||||||
 | 
					- Support for multiple input and output resumes.
 | 
				
			||||||
 | 
					- Convert between FRESH and JSON Resume resumes.
 | 
				
			||||||
 | 
					- Use from your command line or [desktop][7].
 | 
				
			||||||
- Free and open-source through the MIT license.
 | 
					- Free and open-source through the MIT license.
 | 
				
			||||||
- Forthcoming: StackOverflow and LinkedIn support.
 | 
					- Updated daily / weekly. Contributions are [welcome](CONTRIBUTING.md).
 | 
				
			||||||
- Forthcoming: More themes!
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Install
 | 
					## Install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FluentCV requires a recent version of [Node.js][4] and [NPM][5]. Then:
 | 
					Install the latest stable version of HackMyResume with NPM:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. (Optional, for PDF support) Install the latest official [wkhtmltopdf][3] binary for your platform.
 | 
					```bash
 | 
				
			||||||
2. Install **fluentCV** by running `npm install fluentcv -g`.
 | 
					[sudo] npm install hackmyresume -g
 | 
				
			||||||
3. You're ready to go.
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Alternately, install the latest bleeding-edge version (updated daily):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					[sudo] npm install hacksalot/hackmyresume#dev -g
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installing PDF Support (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume tries not to impose a specific PDF engine requirement on
 | 
				
			||||||
 | 
					the user, but will instead work with whatever PDF engines you have installed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Currently, HackMyResume's PDF generation requires either [Phantom.js][2] or
 | 
				
			||||||
 | 
					[wkhtmltopdf][3] to be installed on your system and the `phantomjs` and/or
 | 
				
			||||||
 | 
					`wkhtmltopdf` binaries to be accessible on your PATH. This is an optional
 | 
				
			||||||
 | 
					requirement for users who care about PDF formats. If you don't care about PDF
 | 
				
			||||||
 | 
					formats, skip this step.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installing Themes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume supports both [FRESH][fresh-themes] and [JSON Resume][jrst]-style
 | 
				
			||||||
 | 
					résumé themes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- FRESH themes currently come preinstalled with HackMyResume.
 | 
				
			||||||
 | 
					- JSON Resume themes can be installed from NPM, GitHub, or manually.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To install a JSON Resume theme, just `cd` to the folder where you want to store
 | 
				
			||||||
 | 
					your themes and run one of:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Install with NPM
 | 
				
			||||||
 | 
					npm install jsonresume-theme-[theme-name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Install with GitHub
 | 
				
			||||||
 | 
					git clone https://github.com/[user-or-org]/[repo-name]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then when you're ready to generate your resume, just reference the location of
 | 
				
			||||||
 | 
					the theme folder as you installed it:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO out/resume.all -t node_modules/jsonresume-theme-classy
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note: You can use install themes anywhere on your file system. You don't need a
 | 
				
			||||||
 | 
					package.json or other NPM/Node infrastructure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Getting Started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To use HackMyResume you'll need to create a valid resume in either
 | 
				
			||||||
 | 
					[FRESH][fresca] or [JSON Resume][6] format. Then you can start using the command
 | 
				
			||||||
 | 
					line tool. There are five basic commands you should be aware of:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **build** generates resumes in HTML, Word, Markdown, PDF, and other formats.
 | 
				
			||||||
 | 
					Use it when you need to submit, upload, print, or email resumes in specific
 | 
				
			||||||
 | 
					formats.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    # hackmyresume BUILD <INPUTS...> TO <OUTPUTS...> [-t THEME]
 | 
				
			||||||
 | 
					    hackmyresume BUILD resume.json TO out/resume.all
 | 
				
			||||||
 | 
					    hackmyresume BUILD r1.json r2.json TO out/rez.html out/rez.md foo/rez.all
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **new** creates a new resume in FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    # hackmyresume NEW <OUTPUTS...> [-f <FORMAT>]
 | 
				
			||||||
 | 
					    hackmyresume NEW resume.json
 | 
				
			||||||
 | 
					    hackmyresume NEW resume.json -f fresh
 | 
				
			||||||
 | 
					    hackmyresume NEW r1.json r2.json -f jrs
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **analyze** inspects your resume for keywords, duration, and other metrics.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    # hackmyresume ANALYZE <INPUTS...>
 | 
				
			||||||
 | 
					    hackmyresume ANALYZE resume.json
 | 
				
			||||||
 | 
					    hackmyresume ANALYZE r1.json r2.json
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **convert** converts your source resume between FRESH and JSON Resume
 | 
				
			||||||
 | 
					formats. Use it to convert between the two formats to take advantage of tools
 | 
				
			||||||
 | 
					and services.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    # hackmyresume CONVERT <INPUTS...> TO <OUTPUTS...>
 | 
				
			||||||
 | 
					    hackmyresume CONVERT resume.json TO resume-jrs.json
 | 
				
			||||||
 | 
					    hackmyresume CONVERT 1.json 2.json 3.json TO out/1.json out/2.json out/3.json
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **validate** validates the specified resume against either the FRESH or JSON
 | 
				
			||||||
 | 
					Resume schema. Use it to make sure your resume data is sufficient and complete.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    # hackmyresume VALIDATE <INPUTS...>
 | 
				
			||||||
 | 
					    hackmyresume VALIDATE resume.json
 | 
				
			||||||
 | 
					    hackmyresume VALIDATE r1.json r2.json r3.json
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **peek** echoes your resume or any field, property, or object path on your
 | 
				
			||||||
 | 
					resume to standard output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    # hackmyresume PEEK <INPUTS...> [OBJECT-PATH]
 | 
				
			||||||
 | 
					    hackmyresume PEEK rez.json # Echo the whole resume
 | 
				
			||||||
 | 
					    hackmyresume PEEK rez.json info.brief # Echo the "info.brief" field
 | 
				
			||||||
 | 
					    hackmyresume PEEK rez.json employment.history[1] # Echo the 1st job
 | 
				
			||||||
 | 
					    hackmyresume PEEK rez.json rez2.json info.brief # Compare value
 | 
				
			||||||
 | 
					    ```    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Supported Output Formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume supports these output formats:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Output Format | Ext | Notes
 | 
				
			||||||
 | 
					------------- | --- | -----
 | 
				
			||||||
 | 
					HTML | .html | A standard HTML 5 + CSS resume format that can be viewed in a browser, deployed to a website, etc.
 | 
				
			||||||
 | 
					Markdown | .md | A structured Markdown document that can be used as-is or used to generate HTML.
 | 
				
			||||||
 | 
					LaTeX | .tex | A structured LaTeX document (or collection of documents) that can be processed with pdflatex, xelatex, and similar tools.
 | 
				
			||||||
 | 
					MS Word | .doc | A Microsoft Word office document (XML-driven; WordProcessingML).
 | 
				
			||||||
 | 
					Adobe Acrobat (PDF) | .pdf | A binary PDF document driven by an HTML theme (through wkhtmltopdf).
 | 
				
			||||||
 | 
					plain text | .txt | A formatted plain text document appropriate for emails or copy-paste.
 | 
				
			||||||
 | 
					JSON | .json | A JSON representation of the resume.
 | 
				
			||||||
 | 
					YAML | .yml | A YAML representation of the resume.
 | 
				
			||||||
 | 
					RTF | .rtf | Forthcoming.
 | 
				
			||||||
 | 
					Textile | .textile | Forthcoming.
 | 
				
			||||||
 | 
					image | .png, .bmp | Forthcoming.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Use
 | 
					## Use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Assuming you've got a JSON-formatted resume handy, generating resumes in different formats and combinations easy. Just run:
 | 
					Assuming you've got a JSON-formatted resume handy, generating resumes in
 | 
				
			||||||
 | 
					different formats and combinations is easy. Just run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
fluentcv [inputs] [outputs] [-t theme].
 | 
					hackmyresume BUILD <INPUTS> <OUTPUTS> [-t theme].
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Where `[inputs]` is one or more .json resume files, separated by spaces; `[outputs]` is one or more destination resumes, each prefaced with the `-o` option; and `[theme]` is the desired theme. For example:
 | 
					Where `<INPUTS>` is one or more .json resume files, separated by spaces;
 | 
				
			||||||
 | 
					`<OUTPUTS>` is one or more destination resumes, and `<THEME>` is the desired
 | 
				
			||||||
 | 
					theme (default to Modern). For example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
 | 
					# Generate all resume formats (HTML, PDF, DOC, TXT, YML, etc.)
 | 
				
			||||||
fluentcv resume.json -o out/resume.all -t modern
 | 
					hackmyresume BUILD resume.json TO out/resume.all -t modern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Generate a specific resume format
 | 
					# Generate a specific resume format
 | 
				
			||||||
fluentcv resume.json -o out/resume.html
 | 
					hackmyresume BUILD resume.json TO out/resume.html
 | 
				
			||||||
fluentcv resume.json -o out/resume.pdf
 | 
					hackmyresume BUILD resume.json TO out/resume.pdf
 | 
				
			||||||
fluentcv resume.json -o out/resume.md
 | 
					hackmyresume BUILD resume.json TO out/resume.md
 | 
				
			||||||
fluentcv resume.json -o out/resume.doc
 | 
					hackmyresume BUILD resume.json TO out/resume.doc
 | 
				
			||||||
fluentcv resume.json -o out/resume.json
 | 
					hackmyresume BUILD resume.json TO out/resume.json
 | 
				
			||||||
fluentcv resume.json -o out/resume.txt
 | 
					hackmyresume BUILD resume.json TO out/resume.txt
 | 
				
			||||||
fluentcv resume.json -o out/resume.yml
 | 
					hackmyresume BUILD resume.json TO out/resume.yml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Specify 2 inputs and 3 outputs
 | 
					# Specify 2 inputs and 3 outputs
 | 
				
			||||||
fluentcv in1.json in2.json -o out.html -o out.doc -o out.pdf
 | 
					hackmyresume BUILD in1.json in2.json TO out.html out.doc out.pdf
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You should see something to the effect of:
 | 
					You should see something to the effect of:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
*** FluentCV v0.7.1 ***
 | 
					*** HackMyResume v1.4.0 ***
 | 
				
			||||||
Reading JSON resume: foo/resume.json
 | 
					Reading JSON resume: foo/resume.json
 | 
				
			||||||
Applying MODERN Theme (7 formats)
 | 
					Applying MODERN Theme (7 formats)
 | 
				
			||||||
Generating HTML resume: out/resume.html
 | 
					Generating HTML resume: out/resume.html
 | 
				
			||||||
@@ -74,28 +227,56 @@ Generating YAML resume: out/resume.yml
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Applying a theme
 | 
					### Applying a theme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can specify a predefined or custom theme via the optional `-t` parameter. For a predefined theme, include the theme name. For a custom theme, include the path to the custom theme's folder.
 | 
					HackMyResume can work with any FRESH or JSON Resume theme (the latter must be
 | 
				
			||||||
 | 
					installed first). To specify a theme when generating your resume, use the `-t`
 | 
				
			||||||
 | 
					or `--theme` parameter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
fluentcv resume.json -t modern
 | 
					hackmyresume BUILD resume.json TO out/rez.all -t [theme]
 | 
				
			||||||
fluentcv resume.json -t ~/foo/bar/my-custom-theme/
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
As of v0.7.1, available predefined themes are `modern`, `minimist`, and `hello-world`.
 | 
					The `[theme]` parameter can be the name of a predefined theme OR the path to any
 | 
				
			||||||
 | 
					FRESH or JSON Resume theme folder:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO out/rez.all -t modern
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO OUT.rez.all -t ../some-folder/my-custom-theme/
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO OUT.rez.all -t node_modules/jsonresume-theme-classy
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FRESH themes are currently pre-installed with HackMyResume. JSON Resume themes
 | 
				
			||||||
 | 
					can be installed prior to use:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Install a JSON Resume theme into a local node_modules subfolder:
 | 
				
			||||||
 | 
					npm install jsonresume-theme-[name]
 | 
				
			||||||
 | 
					# Use it with HackMyResume
 | 
				
			||||||
 | 
					hackmyresume build resume.json -t node_modules/jsonresume-theme-[name]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As of v1.6.0, available predefined FRESH themes are `positive`, `modern`,
 | 
				
			||||||
 | 
					`compact`, `minimist`, and `hello-world`. For a list of JSON Resume themes,
 | 
				
			||||||
 | 
					check the [NPM Registry](https://www.npmjs.com/search?q=jsonresume-theme).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Merging resumes
 | 
					### Merging resumes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can **merge multiple resumes together** by specifying them in order from most generic to most specific:
 | 
					You can **merge multiple resumes together** by specifying them in order from
 | 
				
			||||||
 | 
					most generic to most specific:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Merge specific.json onto base.json and generate all formats
 | 
					# Merge specific.json onto base.json and generate all formats
 | 
				
			||||||
fluentcv base.json specific.json -o resume.all
 | 
					hackmyresume BUILD base.json specific.json TO resume.all
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This can be useful for overriding a base (generic) resume with information from a specific (targeted) resume. For example, you might override your generic catch-all "software developer" resume with specific details from your targeted "game developer" resume, or combine two partial resumes into a "complete" resume. Merging follows conventional [extend()][9]-style behavior and there's no arbitrary limit to how many resumes you can merge:
 | 
					This can be useful for overriding a base (generic) resume with information from
 | 
				
			||||||
 | 
					a specific (targeted) resume. For example, you might override your generic
 | 
				
			||||||
 | 
					catch-all "software developer" resume with specific details from your targeted
 | 
				
			||||||
 | 
					"game developer" resume, or combine two partial resumes into a "complete"
 | 
				
			||||||
 | 
					resume. Merging follows conventional [extend()][9]-style behavior and there's
 | 
				
			||||||
 | 
					no arbitrary limit to how many resumes you can merge:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
fluentcv in1.json in2.json in3.json in4.json -o out.html -o out.doc
 | 
					hackmyresume BUILD in1.json in2.json in3.json in4.json TO out.html out.doc
 | 
				
			||||||
Reading JSON resume: in1.json
 | 
					Reading JSON resume: in1.json
 | 
				
			||||||
Reading JSON resume: in2.json
 | 
					Reading JSON resume: in2.json
 | 
				
			||||||
Reading JSON resume: in3.json
 | 
					Reading JSON resume: in3.json
 | 
				
			||||||
@@ -107,37 +288,231 @@ Generating WORD resume: out.doc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Multiple targets
 | 
					### Multiple targets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can specify **multiple output targets** and FluentCV will build them:
 | 
					You can specify **multiple output targets** and HackMyResume will build them:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Generate out1.doc, out1.pdf, and foo.txt from me.json.
 | 
					# Generate out1.doc, out1.pdf, and foo.txt from me.json.
 | 
				
			||||||
fluentcv me.json -o out1.doc -o out1.pdf -o foo.txt
 | 
					hackmyresume BUILD me.json TO out1.doc out1.pdf foo.txt
 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
You can also omit the output file(s) and/or theme completely:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
# Equivalent to "fluentcv resume.json resume.all -t modern"
 | 
					 | 
				
			||||||
fluentcv resume.json
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Using .all
 | 
					### Using .all
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The special `.all` extension tells FluentCV to generate all supported output formats for the given resume. For example, this...
 | 
					The special `.all` extension tells HackMyResume to generate all supported output
 | 
				
			||||||
 | 
					formats for the given resume. For example, this...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
 | 
					# Generate all resume formats (HTML, PDF, DOC, TXT, etc.)
 | 
				
			||||||
fluentcv me.json -o out/resume.all
 | 
					hackmyresume BUILD me.json TO out/resume.all
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
..tells FluentCV to read `me.json` and generate `out/resume.md`, `out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and `out/resume.json`.
 | 
					..tells HackMyResume to read `me.json` and generate `out/resume.md`,
 | 
				
			||||||
 | 
					`out/resume.doc`, `out/resume.html`, `out/resume.txt`, `out/resume.pdf`, and
 | 
				
			||||||
 | 
					`out/resume.json`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Building PDFs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Users who don't care about PDFs can turn off PDF generation across all themes
 | 
				
			||||||
 | 
					and formats with the `--pdf none` switch.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume takes a unique approach to PDF generation. Instead of enforcing
 | 
				
			||||||
 | 
					a specific PDF engine on users, HackMyResume will attempt to work with whatever
 | 
				
			||||||
 | 
					PDF engine you have installed through the engine's command-line interface (CLI).
 | 
				
			||||||
 | 
					Currently that means one or both of...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [wkhtmltopdf][3]
 | 
				
			||||||
 | 
					- [Phantom.js][3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					..with support for other engines planned in the future. But for now, **one or
 | 
				
			||||||
 | 
					both of these engines must be installed and accessible on your PATH in order to
 | 
				
			||||||
 | 
					generate PDF resumes with HackMyResume**. That means you should be able to
 | 
				
			||||||
 | 
					invoke either of these tools directly from your shell or terminal without error:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					wkhtmltopdf input.html output.pdf
 | 
				
			||||||
 | 
					phantomjs script.js input.html output.pdf
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Assuming you've installed one or both of these engines on your system, you can
 | 
				
			||||||
 | 
					tell HackMyResume which flavor of PDF generation to use via the `--pdf` option
 | 
				
			||||||
 | 
					(`-p` for short):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO out.all --pdf phantom
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO out.all --pdf wkhtmltopdf
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json TO out.all --pdf none
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Analyzing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume can analyze your resume for keywords, employment gaps, and other
 | 
				
			||||||
 | 
					metrics. Run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume ANALYZE <my-resume>.json
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Depending on the HackMyResume version, you should see output similar to:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					*** HackMyResume v1.6.0 ***
 | 
				
			||||||
 | 
					Reading resume: resume.json
 | 
				
			||||||
 | 
					Analyzing FRESH resume: resume.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SECTIONS (10):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        employment:    12
 | 
				
			||||||
 | 
					         education:     2
 | 
				
			||||||
 | 
					           service:     1
 | 
				
			||||||
 | 
					            skills:     8
 | 
				
			||||||
 | 
					           writing:     1
 | 
				
			||||||
 | 
					       recognition:     0
 | 
				
			||||||
 | 
					            social:     4
 | 
				
			||||||
 | 
					         interests:     2
 | 
				
			||||||
 | 
					        references:     1
 | 
				
			||||||
 | 
					         languages:     2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COVERAGE (61.1%):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Total Days:  6034
 | 
				
			||||||
 | 
					          Employed:  3688
 | 
				
			||||||
 | 
					              Gaps:     8  [31, 1065, 273, 153, 671, 61, 61, 31]
 | 
				
			||||||
 | 
					          Overlaps:     1  [243]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					KEYWORDS (61):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           Node.js:     6 mentions
 | 
				
			||||||
 | 
					        JavaScript:     9 mentions
 | 
				
			||||||
 | 
					        SQL Server:     3 mentions
 | 
				
			||||||
 | 
					     Visual Studio:     6 mentions
 | 
				
			||||||
 | 
					           Web API:     1 mentions
 | 
				
			||||||
 | 
					   N-tier / 3-tier:     1 mentions
 | 
				
			||||||
 | 
					            HTML 5:     1 mentions
 | 
				
			||||||
 | 
					        JavaScript:     6 mentions
 | 
				
			||||||
 | 
					               CSS:     2 mentions
 | 
				
			||||||
 | 
					Sass / LESS / SCSS:     1 mentions
 | 
				
			||||||
 | 
					              LAMP:     3 mentions
 | 
				
			||||||
 | 
					              WISC:     1 mentions
 | 
				
			||||||
 | 
					              HTTP:    21 mentions
 | 
				
			||||||
 | 
					              JSON:     1 mentions
 | 
				
			||||||
 | 
					               XML:     2 mentions
 | 
				
			||||||
 | 
					              REST:     1 mentions
 | 
				
			||||||
 | 
					        WebSockets:     2 mentions
 | 
				
			||||||
 | 
					       Backbone.js:     3 mentions
 | 
				
			||||||
 | 
					        Angular.js:     1 mentions
 | 
				
			||||||
 | 
					           Node.js:     4 mentions
 | 
				
			||||||
 | 
					               NPM:     1 mentions
 | 
				
			||||||
 | 
					             Bower:     1 mentions
 | 
				
			||||||
 | 
					             Grunt:     2 mentions
 | 
				
			||||||
 | 
					              Gulp:     1 mentions
 | 
				
			||||||
 | 
					            jQuery:     2 mentions
 | 
				
			||||||
 | 
					         Bootstrap:     3 mentions
 | 
				
			||||||
 | 
					     Underscore.js:     1 mentions
 | 
				
			||||||
 | 
					         PhantomJS:     1 mentions
 | 
				
			||||||
 | 
					      CoffeeScript:     1 mentions
 | 
				
			||||||
 | 
					            Python:    11 mentions
 | 
				
			||||||
 | 
					              Perl:     4 mentions
 | 
				
			||||||
 | 
					               PHP:     7 mentions
 | 
				
			||||||
 | 
					             MySQL:    12 mentions
 | 
				
			||||||
 | 
					        PostgreSQL:     4 mentions
 | 
				
			||||||
 | 
					             NoSQL:     2 mentions
 | 
				
			||||||
 | 
					            Apache:     2 mentions
 | 
				
			||||||
 | 
					               AWS:     2 mentions
 | 
				
			||||||
 | 
					               EC2:     2 mentions
 | 
				
			||||||
 | 
					               RDS:     3 mentions
 | 
				
			||||||
 | 
					                S3:     1 mentions
 | 
				
			||||||
 | 
					             Azure:     1 mentions
 | 
				
			||||||
 | 
					         Rackspace:     1 mentions
 | 
				
			||||||
 | 
					               C++:    23 mentions
 | 
				
			||||||
 | 
					            C++ 11:     1 mentions
 | 
				
			||||||
 | 
					             Boost:     1 mentions
 | 
				
			||||||
 | 
					             Xcode:     2 mentions
 | 
				
			||||||
 | 
					               gcc:     1 mentions
 | 
				
			||||||
 | 
					             OO&AD:     1 mentions
 | 
				
			||||||
 | 
					              .NET:    20 mentions
 | 
				
			||||||
 | 
					           Unity 5:     2 mentions
 | 
				
			||||||
 | 
					              Mono:     3 mentions
 | 
				
			||||||
 | 
					       MonoDevelop:     1 mentions
 | 
				
			||||||
 | 
					           Xamarin:     1 mentions
 | 
				
			||||||
 | 
					             TOTAL:   180 mentions
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Validating
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume can also validate your resumes against either the [FRESH /
 | 
				
			||||||
 | 
					FRESCA][fresca] or [JSON Resume][6] formats. To validate one or more existing
 | 
				
			||||||
 | 
					resumes, use the `validate` command:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Validate myresume.json against either the FRESH or JSON Resume schema.
 | 
				
			||||||
 | 
					hackmyresume VALIDATE resumeA.json resumeB.json
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume will validate each specified resume in turn:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					*** HackMyResume v1.6.0 ***
 | 
				
			||||||
 | 
					Validating JSON resume: resumeA.json (INVALID)
 | 
				
			||||||
 | 
					Validating JSON resume: resumeB.json (VALID)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Converting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume can convert between the [FRESH][fresca] and [JSON Resume][6]
 | 
				
			||||||
 | 
					formats. Just run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume CONVERT <INPUTS> <OUTPUTS>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					where <INPUTS> is one or more resumes in FRESH or JSON Resume format, and
 | 
				
			||||||
 | 
					<OUTPUTS> is a corresponding list of output file names. HackMyResume will
 | 
				
			||||||
 | 
					autodetect the format (FRESH or JRS) of each input resume and convert it to the
 | 
				
			||||||
 | 
					other format (JRS or FRESH).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### File-based Options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can pass options into HackMyResume via an external options or ".hackmyrc"
 | 
				
			||||||
 | 
					file with the `--options` or `-o` switch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json -o path/to/options.json
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The options file can contain any documented HackMyResume option, including
 | 
				
			||||||
 | 
					`theme`, `silent`, `debug`, `pdf`, `css`, and other settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  // Set the default theme to "compact"
 | 
				
			||||||
 | 
					  "theme": "compact",
 | 
				
			||||||
 | 
					  // Change the "employment" section title text to "Work"
 | 
				
			||||||
 | 
					  "sectionTitles": {
 | 
				
			||||||
 | 
					    "employment": "Work"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If a particular option is specified both on the command line and in an external
 | 
				
			||||||
 | 
					options file, the explicit command-line option takes precedence.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# path/to/options.json specifes the POSITIVE theme
 | 
				
			||||||
 | 
					# -t parameter specifies the COMPACT theme
 | 
				
			||||||
 | 
					# The -t parameter wins.
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json -o path/to/options.json -t compact
 | 
				
			||||||
 | 
					> Reading resume: resume.json
 | 
				
			||||||
 | 
					> Applying COMPACT theme (7 formats)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Prettifying
 | 
					### Prettifying
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FluentCV applies [js-beautify][10]-style HTML prettification by default to HTML-formatted resumes. To disable prettification, the `--nopretty` or `-n` flag can be used:
 | 
					HackMyResume applies [js-beautify][10]-style HTML prettification by default to
 | 
				
			||||||
 | 
					HTML-formatted resumes. To disable prettification, the `--no-prettify` or `-n`
 | 
				
			||||||
 | 
					flag can be used:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
fluentcv resume.json out.all --nopretty
 | 
					hackmyresume BUILD resume.json out.all --no-prettify
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Silent Mode
 | 
					### Silent Mode
 | 
				
			||||||
@@ -145,10 +520,26 @@ fluentcv resume.json out.all --nopretty
 | 
				
			|||||||
Use `-s` or `--silent` to run in silent mode:
 | 
					Use `-s` or `--silent` to run in silent mode:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
fluentcv resume.json -o someFile.all -s
 | 
					hackmyresume BUILD resume.json -o someFile.all -s
 | 
				
			||||||
fluentcv resume.json -o someFile.all --silent
 | 
					hackmyresume BUILD resume.json -o someFile.all --silent
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Debug Mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use `-d` or `--debug` to force HMR to emit a call stack when errors occur. In
 | 
				
			||||||
 | 
					the future, this option will emit detailed error logging.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					hackmyresume BUILD resume.json -d
 | 
				
			||||||
 | 
					hackmyresume ANALYZE resume.json --debug
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume is a community-driven free and open source project under the MIT
 | 
				
			||||||
 | 
					License. Contributions are encouraged and we respond to all PRs and issues,
 | 
				
			||||||
 | 
					usually within 24 hours. See [CONTRIBUTING.md][contribute] for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MIT. Go crazy. See [LICENSE.md][1] for details.
 | 
					MIT. Go crazy. See [LICENSE.md][1] for details.
 | 
				
			||||||
@@ -163,3 +554,15 @@ MIT. Go crazy. See [LICENSE.md][1] for details.
 | 
				
			|||||||
[8]: https://youtu.be/N9wsjroVlu8
 | 
					[8]: https://youtu.be/N9wsjroVlu8
 | 
				
			||||||
[9]: https://api.jquery.com/jquery.extend/
 | 
					[9]: https://api.jquery.com/jquery.extend/
 | 
				
			||||||
[10]: https://github.com/beautify-web/js-beautify
 | 
					[10]: https://github.com/beautify-web/js-beautify
 | 
				
			||||||
 | 
					[fresh]: https://github.com/fluentdesk/FRESH
 | 
				
			||||||
 | 
					[fresca]: https://github.com/fluentdesk/FRESCA
 | 
				
			||||||
 | 
					[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
 | 
				
			||||||
 | 
					[img-release]: https://img.shields.io/github/release/hacksalot/HackMyResume.svg?label=version
 | 
				
			||||||
 | 
					[img-master]: https://img.shields.io/travis/hacksalot/HackMyResume/master.svg
 | 
				
			||||||
 | 
					[img-dev]: https://img.shields.io/travis/hacksalot/HackMyResume/dev.svg?label=dev
 | 
				
			||||||
 | 
					[travis-url-master]: https://travis-ci.org/hacksalot/HackMyResume?branch=master
 | 
				
			||||||
 | 
					[travis-url-dev]: https://travis-ci.org/hacksalot/HackMyResume?branch=dev
 | 
				
			||||||
 | 
					[latest-release]: https://github.com/hacksalot/HackMyResume/releases/latest
 | 
				
			||||||
 | 
					[contribute]: CONTRIBUTING.md
 | 
				
			||||||
 | 
					[fresh-themes]: https://github.com/fluentdesk/fresh-themes
 | 
				
			||||||
 | 
					[jrst]: https://www.npmjs.com/search?q=jsonresume-theme
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										107
									
								
								ROADMAP.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ROADMAP.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					Development Roadmap
 | 
				
			||||||
 | 
					===================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Short-Term
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### FluentCV Desktop: Beta 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The **FluentCV Desktop 1.0 beta release** will present HackMyResume
 | 
				
			||||||
 | 
					functionality in a cross-platform desktop application for OS X, Linux, and
 | 
				
			||||||
 | 
					Windows.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### GitHub Integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume will offer GitHub integration for versioned resume storage and
 | 
				
			||||||
 | 
					retrieval via the `COMMIT` or `STORE` command(s) starting in 1.7.0 or 1.8.0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### fresh-themes 1.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The **fresh-themes 1.0** release will bring 100% coverage of the FRESH and JRS
 | 
				
			||||||
 | 
					object models—all resume sections and fields—along with
 | 
				
			||||||
 | 
					documentation, theme developer's guide, new themes, and a freeze to the FRESH
 | 
				
			||||||
 | 
					theme structure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Better LaTeX support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Including Markdown-to-LaTeX translation and more LaTeX-driven themes / formats.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### StackOverflow and LinkedIn support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Will start appearing in v1.7.0, with incremental improvements in 1.8.0 and
 | 
				
			||||||
 | 
					beyond.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Improved resume sorting and arranging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Better resume sorting** of items and sections: ascending, descending, by
 | 
				
			||||||
 | 
					date or other criteria ([#67][i67]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Remote resume / theme loading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Support remote loading of themes and resumes over `http`, `https`, and
 | 
				
			||||||
 | 
					`git://`. Enable these usage patterns:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash
 | 
				
			||||||
 | 
					    hackmyresume build https://somesite.com/my-resume.json -t informatic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hackmyresume build resume.json -t npm:fresh-theme-ergonomic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hackmyresume analyze https://github.com/foo/my-resume
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 100% code coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Should reduce certain classes of errors and allow HMR to display a nifty 100%
 | 
				
			||||||
 | 
					code coverage badge.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Improved **documentation and samples**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Expanded documentation and samples throughout.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Mid-Term
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Cover letters and job descriptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Add support for schema-driven **cover letters** and **job descriptions**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Character Sheets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HackMyResume 2.0 will ship with support for, yes, RPG-style character sheets.
 | 
				
			||||||
 | 
					This will demonstrate the tool's ability to flow arbitrary JSON to concrete
 | 
				
			||||||
 | 
					document(s) and provide unique albeit niche functionality around various games
 | 
				
			||||||
 | 
					([#117][i117]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Rich text (.rtf) output formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Basic support for **rich text** `.rtf` output formats.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Investigate: groff support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Investigate adding [**groff**][groff] support, because that would, indeed, be
 | 
				
			||||||
 | 
					[dope][d] ([#37][i37]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Investigate: org-mode support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Investigate adding [**org mode**][om] support ([#38][i38]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Investigate: Scribus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Investigate adding [**Scribus SLA**][scri] support ([#54][i54]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Support JSON Resume 1.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When released.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Long-Term
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- TBD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[groff]: http://www.gnu.org/software/groff/
 | 
				
			||||||
 | 
					[om]: http://orgmode.org/
 | 
				
			||||||
 | 
					[scri]: https://en.wikipedia.org/wiki/Scribus
 | 
				
			||||||
 | 
					[d]: https://github.com/hacksalot/HackMyResume/issues/37#issue-123818674
 | 
				
			||||||
 | 
					[i37]: https://github.com/hacksalot/HackMyResume/issues/37
 | 
				
			||||||
 | 
					[i38]: https://github.com/hacksalot/HackMyResume/issues/38
 | 
				
			||||||
 | 
					[i54]: https://github.com/hacksalot/HackMyResume/issues/54
 | 
				
			||||||
 | 
					[i67]: https://github.com/hacksalot/HackMyResume/issues/67
 | 
				
			||||||
 | 
					[i107]: https://github.com/hacksalot/HackMyResume/issues/107
 | 
				
			||||||
 | 
					[i117]: https://github.com/hacksalot/HackMyResume/issues/117
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 73 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/hackmyresume.cli.1.6.0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/hackmyresume.cli.1.6.0.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 83 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/hackmyresume_cli.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/hackmyresume_cli.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 83 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/resume-bouqet.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/resume-bouqet.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 170 KiB  | 
							
								
								
									
										30
									
								
								dist/cli/analyze.hbs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								dist/cli/analyze.hbs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					{{style "SECTIONS (" "bold"}}{{style totals.numSections "white" }}{{style ")" "bold"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        employment:     {{v totals.totals.employment "-" 2 "bold" }}
 | 
				
			||||||
 | 
					          projects:     {{v totals.totals.projects "-" 2 "bold" }}
 | 
				
			||||||
 | 
					         education:     {{v totals.totals.education "-" 2 "bold" }}
 | 
				
			||||||
 | 
					           service:     {{v totals.totals.service "-" 2 "bold" }}
 | 
				
			||||||
 | 
					            skills:     {{v totals.totals.skills "-" 2 "bold" }}
 | 
				
			||||||
 | 
					           writing:     {{v totals.totals.writing "-" 2 "bold" }}
 | 
				
			||||||
 | 
					          speaking:     {{v totals.totals.speaking "-" 2 "bold" }}
 | 
				
			||||||
 | 
					           reading:     {{v totals.totals.reading "-" 2 "bold" }}
 | 
				
			||||||
 | 
					            social:     {{v totals.totals.social "-" 2 "bold" }}
 | 
				
			||||||
 | 
					        references:     {{v totals.totals.references "-" 2 "bold" }}
 | 
				
			||||||
 | 
					      testimonials:     {{v totals.totals.testimonials "-" 2 "bold" }}
 | 
				
			||||||
 | 
					         languages:     {{v totals.totals.languages "-" 2 "bold" }}
 | 
				
			||||||
 | 
					         interests:     {{v totals.totals.interests "-" 2 "bold" }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{style "COVERAGE (" "bold"}}{{style coverage.pct "white"}}{{style ")" "bold"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Total Days:  {{v coverage.duration.total "-" 5 "bold" }}
 | 
				
			||||||
 | 
					          Employed:  {{v coverage.duration.work "-" 5 "bold" }}
 | 
				
			||||||
 | 
					              Gaps:  {{v coverage.gaps.length "-" 5 "bold" }}  [{{#if coverage.gaps.length }}{{#each coverage.gaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}]
 | 
				
			||||||
 | 
					          Overlaps:  {{v coverage.overlaps.length "-" 5 "bold" }}  [{{#if coverage.overlaps.length }}{{#each coverage.overlaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{style "KEYWORDS (" "bold"}}{{style keywords.length "white" }}{{style ")" "bold"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{#each keywords }}{{{pad name 18}}}:  {{v count "-" 5 "bold"}} mention{{#isPlural count}}s{{/isPlural}}
 | 
				
			||||||
 | 
					{{/each}}
 | 
				
			||||||
 | 
					    -------------------------------
 | 
				
			||||||
 | 
					{{v keywords.length "0" 9 "bold"}} {{style "KEYWORDS" "bold"}}   {{v keywords.totalKeywords "0" 5 "bold"}} {{style "mentions" "bold"}}
 | 
				
			||||||
							
								
								
									
										248
									
								
								dist/cli/error.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								dist/cli/error.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,248 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Error-handling routines for HackMyResume.
 | 
				
			||||||
 | 
					@module cli/error
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var ErrorHandler, FCMD, FS, HMSTATUS, M2C, PATH, PKG, SyntaxErrorEx, WRAP, YAML, _defaultLog, assembleError, chalk, extend, printf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PKG = require('../../package.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FCMD = require('../index');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WRAP = require('word-wrap');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  M2C = require('../utils/md2chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extend = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  YAML = require('yamljs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  printf = require('printf');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SyntaxErrorEx = require('../utils/syntax-error-ex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('string.prototype.startswith');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Error handler for HackMyResume. All errors are handled here.
 | 
				
			||||||
 | 
					  @class ErrorHandler
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ErrorHandler = module.exports = {
 | 
				
			||||||
 | 
					    init: function(debug, assert, silent) {
 | 
				
			||||||
 | 
					      this.debug = debug;
 | 
				
			||||||
 | 
					      this.assert = assert;
 | 
				
			||||||
 | 
					      this.silent = silent;
 | 
				
			||||||
 | 
					      this.msgs = require('./msg').errors;
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    err: function(ex, shouldExit) {
 | 
				
			||||||
 | 
					      var o, objError, stack, stackTrace;
 | 
				
			||||||
 | 
					      o = this.silent ? function() {} : _defaultLog;
 | 
				
			||||||
 | 
					      if (ex.pass) {
 | 
				
			||||||
 | 
					        throw ex;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.msgs = this.msgs || require('./msg').errors;
 | 
				
			||||||
 | 
					      if (ex.fluenterror) {
 | 
				
			||||||
 | 
					        objError = assembleError.call(this, ex);
 | 
				
			||||||
 | 
					        o(this['format_' + objError.etype](objError.msg));
 | 
				
			||||||
 | 
					        if (objError.withStack) {
 | 
				
			||||||
 | 
					          stack = ex.stack || (ex.inner && ex.inner.stack);
 | 
				
			||||||
 | 
					          stack && o(chalk.gray(stack));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (shouldExit) {
 | 
				
			||||||
 | 
					          if (this.debug) {
 | 
				
			||||||
 | 
					            o(chalk.cyan('Exiting with error code ' + ex.fluenterror.toString()));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (this.assert) {
 | 
				
			||||||
 | 
					            ex.pass = true;
 | 
				
			||||||
 | 
					            throw ex;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return process.exit(ex.fluenterror);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        o(ex);
 | 
				
			||||||
 | 
					        stackTrace = ex.stack || (ex.inner && ex.inner.stack);
 | 
				
			||||||
 | 
					        if (stackTrace && this.debug) {
 | 
				
			||||||
 | 
					          return o(M2C(ex.stack || ex.inner.stack, 'gray'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    format_error: function(msg) {
 | 
				
			||||||
 | 
					      msg = msg || '';
 | 
				
			||||||
 | 
					      return chalk.red.bold(msg.toUpperCase().startsWith('ERROR:') ? msg : 'Error: ' + msg);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    format_warning: function(brief, msg) {
 | 
				
			||||||
 | 
					      return chalk.yellow(brief) + chalk.yellow(msg || '');
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    format_custom: function(msg) {
 | 
				
			||||||
 | 
					      return msg;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _defaultLog = function() {
 | 
				
			||||||
 | 
					    return console.log.apply(console.log, arguments);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  assembleError = function(ex) {
 | 
				
			||||||
 | 
					    var etype, msg, quit, se, withStack;
 | 
				
			||||||
 | 
					    msg = '';
 | 
				
			||||||
 | 
					    withStack = false;
 | 
				
			||||||
 | 
					    quit = false;
 | 
				
			||||||
 | 
					    etype = 'warning';
 | 
				
			||||||
 | 
					    if (this.debug) {
 | 
				
			||||||
 | 
					      withStack = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    switch (ex.fluenterror) {
 | 
				
			||||||
 | 
					      case HMSTATUS.themeNotFound:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.themeNotFound.msg, 'yellow'), ex.data);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.copyCSS:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.copyCSS.msg, 'red');
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.resumeNotFound:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.resumeNotFound.msg, 'yellow');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.missingCommand:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.missingCommand.msg + " (", 'yellow');
 | 
				
			||||||
 | 
					        msg += Object.keys(FCMD.verbs).map(function(v, idx, ar) {
 | 
				
			||||||
 | 
					          return (idx === ar.length - 1 ? chalk.yellow('or ') : '') + chalk.yellow.bold(v.toUpperCase());
 | 
				
			||||||
 | 
					        }).join(chalk.yellow(', ')) + chalk.yellow(").\n\n");
 | 
				
			||||||
 | 
					        msg += chalk.gray(FS.readFileSync(PATH.resolve(__dirname, '../cli/use.txt'), 'utf8'));
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.invalidCommand:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.invalidCommand.msg, 'yellow'), ex.attempted);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.resumeNotFoundAlt:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.resumeNotFoundAlt.msg, 'yellow');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.inputOutputParity:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.inputOutputParity.msg);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.createNameMissing:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.createNameMissing.msg);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.pdfGeneration:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.pdfGeneration.msg, 'bold');
 | 
				
			||||||
 | 
					        if (ex.inner) {
 | 
				
			||||||
 | 
					          msg += chalk.red('\n' + ex.inner);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.invalid:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.invalid.msg, 'red');
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.generateError:
 | 
				
			||||||
 | 
					        msg = (ex.inner && ex.inner.toString()) || ex;
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.fileSaveError:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.fileSaveError.msg), (ex.inner || ex).toString());
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.invalidFormat:
 | 
				
			||||||
 | 
					        ex.data.forEach(function(d) {
 | 
				
			||||||
 | 
					          return msg += printf(M2C(this.msgs.invalidFormat.msg, 'bold'), ex.theme.name.toUpperCase(), d.format.toUpperCase());
 | 
				
			||||||
 | 
					        }, this);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.missingParam:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.missingParam.msg), ex.expected, ex.helper);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.invalidHelperUse:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.invalidHelperUse.msg), ex.helper);
 | 
				
			||||||
 | 
					        if (ex.error) {
 | 
				
			||||||
 | 
					          msg += '\n--> ' + assembleError.call(this, extend(true, {}, ex, {
 | 
				
			||||||
 | 
					            fluenterror: ex.error
 | 
				
			||||||
 | 
					          })).msg;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        etype = 'warning';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.notOnPath:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine);
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.readError:
 | 
				
			||||||
 | 
					        if (!ex.quiet) {
 | 
				
			||||||
 | 
					          console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        msg = ex.inner.toString();
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.mixedMerge:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.mixedMerge.msg);
 | 
				
			||||||
 | 
					        quit = false;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.invokeTemplate:
 | 
				
			||||||
 | 
					        msg = M2C(this.msgs.invokeTemplate.msg, 'red');
 | 
				
			||||||
 | 
					        msg += M2C('\n' + WRAP(ex.inner.toString(), {
 | 
				
			||||||
 | 
					          width: 60,
 | 
				
			||||||
 | 
					          indent: '   '
 | 
				
			||||||
 | 
					        }), 'gray');
 | 
				
			||||||
 | 
					        etype = 'custom';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.compileTemplate:
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.themeLoad:
 | 
				
			||||||
 | 
					        msg = M2C(printf(this.msgs.themeLoad.msg, ex.attempted.toUpperCase()), 'red');
 | 
				
			||||||
 | 
					        if (ex.inner && ex.inner.fluenterror) {
 | 
				
			||||||
 | 
					          msg += M2C('\nError: ', 'red') + assembleError.call(this, ex.inner).msg;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        quit = true;
 | 
				
			||||||
 | 
					        etype = 'custom';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.parseError:
 | 
				
			||||||
 | 
					        if (SyntaxErrorEx.is(ex.inner)) {
 | 
				
			||||||
 | 
					          console.error(printf(M2C(this.msgs.readError.msg, 'red'), ex.file));
 | 
				
			||||||
 | 
					          se = new SyntaxErrorEx(ex, ex.raw);
 | 
				
			||||||
 | 
					          if ((se.line != null) && (se.col != null)) {
 | 
				
			||||||
 | 
					            msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), se.line, se.col);
 | 
				
			||||||
 | 
					          } else if (se.line != null) {
 | 
				
			||||||
 | 
					            msg = printf(M2C(this.msgs.parseError.msg[1], 'red'), se.line);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            msg = M2C(this.msgs.parseError.msg[2], 'red');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (ex.inner && (ex.inner.line != null) && (ex.inner.col != null)) {
 | 
				
			||||||
 | 
					          msg = printf(M2C(this.msgs.parseError.msg[0], 'red'), ex.inner.line, ex.inner.col);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          msg = ex;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.createError:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.createError.msg), ex.inner.path);
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case HMSTATUS.validateError:
 | 
				
			||||||
 | 
					        msg = printf(M2C(this.msgs.validateError.msg), ex.inner.toString());
 | 
				
			||||||
 | 
					        etype = 'error';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      msg: msg,
 | 
				
			||||||
 | 
					      withStack: withStack,
 | 
				
			||||||
 | 
					      quit: quit,
 | 
				
			||||||
 | 
					      etype: etype
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=error.js.map
 | 
				
			||||||
							
								
								
									
										22
									
								
								dist/cli/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								dist/cli/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					#! /usr/bin/env node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Command-line interface (CLI) for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module index.js
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('./main')( process.argv );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					catch( ex ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('./error').err( ex, true );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										316
									
								
								dist/cli/main.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								dist/cli/main.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,316 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the `main` function.
 | 
				
			||||||
 | 
					@module cli/main
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var Command, EXTEND, FS, HME, HMR, HMSTATUS, M2C, OUTPUT, PAD, PATH, PKG, StringUtils, _, _err, _exitCallback, _opts, _out, _title, chalk, execute, executeFail, executeSuccess, initOptions, initialize, loadOptions, logMsg, main, printf, safeLoadJSON, splitSrcDest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMR = require('../index');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PKG = require('../../package.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EXTEND = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HME = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  safeLoadJSON = require('../utils/safe-json-loader');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  StringUtils = require('../utils/string.js');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  OUTPUT = require('./out');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PAD = require('string-padding');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Command = require('commander').Command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  M2C = require('../utils/md2chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  printf = require('printf');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _opts = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _title = chalk.white.bold('\n*** HackMyResume v' + PKG.version + ' ***');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _out = new OUTPUT(_opts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _err = require('./error');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _exitCallback = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  A callable implementation of the HackMyResume CLI. Encapsulates the command
 | 
				
			||||||
 | 
					  line interface as a single method accepting a parameter array.
 | 
				
			||||||
 | 
					  @alias module:cli/main.main
 | 
				
			||||||
 | 
					  @param rawArgs {Array} An array of command-line parameters. Will either be
 | 
				
			||||||
 | 
					  process.argv (in production) or custom parameters (in test).
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  main = module.exports = function(rawArgs, exitCallback) {
 | 
				
			||||||
 | 
					    var args, initInfo, program;
 | 
				
			||||||
 | 
					    initInfo = initialize(rawArgs, exitCallback);
 | 
				
			||||||
 | 
					    args = initInfo.args;
 | 
				
			||||||
 | 
					    program = new Command('hackmyresume').version(PKG.version).description(chalk.yellow.bold('*** HackMyResume ***')).option('-s --silent', 'Run in silent mode').option('--no-color', 'Disable colors').option('--color', 'Enable colors').option('-d --debug', 'Enable diagnostics', false).option('-a --assert', 'Treat warnings as errors', false).option('-v --version', 'Show the version').allowUnknownOption();
 | 
				
			||||||
 | 
					    program.jsonArgs = initInfo.options;
 | 
				
			||||||
 | 
					    program.command('new')["arguments"]('<sources...>').option('-f --format <fmt>', 'FRESH or JRS format', 'FRESH').alias('create').description('Create resume(s) in FRESH or JSON RESUME format.').action((function(sources) {
 | 
				
			||||||
 | 
					      execute.call(this, sources, [], this.opts(), logMsg);
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					    program.command('validate')["arguments"]('<sources...>').description('Validate a resume in FRESH or JSON RESUME format.').action(function(sources) {
 | 
				
			||||||
 | 
					      execute.call(this, sources, [], this.opts(), logMsg);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    program.command('convert').description('Convert a resume to/from FRESH or JSON RESUME format.').action(function() {
 | 
				
			||||||
 | 
					      var x;
 | 
				
			||||||
 | 
					      x = splitSrcDest.call(this);
 | 
				
			||||||
 | 
					      execute.call(this, x.src, x.dst, this.opts(), logMsg);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    program.command('analyze')["arguments"]('<sources...>').description('Analyze one or more resumes.').action(function(sources) {
 | 
				
			||||||
 | 
					      execute.call(this, sources, [], this.opts(), logMsg);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    program.command('peek')["arguments"]('<sources...>').description('Peek at a resume field or section').action(function(sources, sectionOrField) {
 | 
				
			||||||
 | 
					      var dst;
 | 
				
			||||||
 | 
					      dst = sources && sources.length > 1 ? [sources.pop()] : [];
 | 
				
			||||||
 | 
					      execute.call(this, sources, dst, this.opts(), logMsg);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    program.command('build').alias('generate').option('-t --theme <theme>', 'Theme name or path').option('-n --no-prettify', 'Disable HTML prettification', true).option('-c --css <option>', 'CSS linking / embedding').option('-p --pdf <engine>', 'PDF generation engine').option('--no-sort', 'Sort resume sections by date', false).option('--tips', 'Display theme tips and warnings.', false).description('Generate resume to multiple formats').action(function(sources, targets, options) {
 | 
				
			||||||
 | 
					      var x;
 | 
				
			||||||
 | 
					      x = splitSrcDest.call(this);
 | 
				
			||||||
 | 
					      execute.call(this, x.src, x.dst, this.opts(), logMsg);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    program.parse(args);
 | 
				
			||||||
 | 
					    if (!program.args.length) {
 | 
				
			||||||
 | 
					      throw {
 | 
				
			||||||
 | 
					        fluenterror: 4
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Massage command-line args and setup Commander.js. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  initialize = function(ar, exitCallback) {
 | 
				
			||||||
 | 
					    var o;
 | 
				
			||||||
 | 
					    _exitCallback = exitCallback || process.exit;
 | 
				
			||||||
 | 
					    o = initOptions(ar);
 | 
				
			||||||
 | 
					    o.silent || logMsg(_title);
 | 
				
			||||||
 | 
					    if (o.debug) {
 | 
				
			||||||
 | 
					      _out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.'));
 | 
				
			||||||
 | 
					      _out.log('');
 | 
				
			||||||
 | 
					      _out.log(chalk.cyan(PAD('  Platform:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(process.platform === 'win32' ? 'windows' : process.platform));
 | 
				
			||||||
 | 
					      _out.log(chalk.cyan(PAD('  Node.js:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(process.version));
 | 
				
			||||||
 | 
					      _out.log(chalk.cyan(PAD('  HackMyResume:', 25, null, PAD.RIGHT)) + chalk.cyan.bold('v' + PKG.version));
 | 
				
			||||||
 | 
					      _out.log(chalk.cyan(PAD('  FRESCA:', 25, null, PAD.RIGHT)) + chalk.cyan.bold(PKG.dependencies.fresca));
 | 
				
			||||||
 | 
					      _out.log('');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _err.init(o.debug, o.assert, o.silent);
 | 
				
			||||||
 | 
					    if (o.verb && !HMR.verbs[o.verb] && !HMR.alias[o.verb]) {
 | 
				
			||||||
 | 
					      _err.err({
 | 
				
			||||||
 | 
					        fluenterror: HMSTATUS.invalidCommand,
 | 
				
			||||||
 | 
					        quit: true,
 | 
				
			||||||
 | 
					        attempted: o.orgVerb
 | 
				
			||||||
 | 
					      }, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Command.prototype.missingArgument = function(name) {
 | 
				
			||||||
 | 
					      _err.err({
 | 
				
			||||||
 | 
					        fluenterror: this.name() !== 'new' ? HMSTATUS.resumeNotFound : HMSTATUS.createNameMissing
 | 
				
			||||||
 | 
					      }, true);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    Command.prototype.helpInformation = function() {
 | 
				
			||||||
 | 
					      var manPage;
 | 
				
			||||||
 | 
					      manPage = FS.readFileSync(PATH.join(__dirname, 'use.txt'), 'utf8');
 | 
				
			||||||
 | 
					      return chalk.green.bold(manPage);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      args: o.args,
 | 
				
			||||||
 | 
					      options: o.json
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Init options prior to setting up command infrastructure. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  initOptions = function(ar) {
 | 
				
			||||||
 | 
					    oVerb;
 | 
				
			||||||
 | 
					    var args, cleanArgs, inf, isAssert, isDebug, isMono, isSilent, oJSON, oVerb, optStr, optsIdx, verb, vidx;
 | 
				
			||||||
 | 
					    verb = '';
 | 
				
			||||||
 | 
					    args = ar.slice();
 | 
				
			||||||
 | 
					    cleanArgs = args.slice(2);
 | 
				
			||||||
 | 
					    oJSON;
 | 
				
			||||||
 | 
					    if (cleanArgs.length) {
 | 
				
			||||||
 | 
					      vidx = _.findIndex(cleanArgs, function(v) {
 | 
				
			||||||
 | 
					        return v[0] !== '-';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (vidx !== -1) {
 | 
				
			||||||
 | 
					        oVerb = cleanArgs[vidx];
 | 
				
			||||||
 | 
					        verb = args[vidx + 2] = oVerb.trim().toLowerCase();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      optsIdx = _.findIndex(cleanArgs, function(v) {
 | 
				
			||||||
 | 
					        return v === '-o' || v === '--options' || v === '--opts';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (optsIdx !== -1) {
 | 
				
			||||||
 | 
					        optStr = cleanArgs[optsIdx + 1];
 | 
				
			||||||
 | 
					        args.splice(optsIdx + 2, 2);
 | 
				
			||||||
 | 
					        if (optStr && (optStr = optStr.trim())) {
 | 
				
			||||||
 | 
					          if (optStr[0] === '{') {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* jshint ignore:start */
 | 
				
			||||||
 | 
					            oJSON = eval('(' + optStr + ')');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* jshint ignore:end */
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            inf = safeLoadJSON(optStr);
 | 
				
			||||||
 | 
					            if (!inf.ex) {
 | 
				
			||||||
 | 
					              oJSON = inf.json;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    isDebug = _.some(args, function(v) {
 | 
				
			||||||
 | 
					      return v === '-d' || v === '--debug';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    isSilent = _.some(args, function(v) {
 | 
				
			||||||
 | 
					      return v === '-s' || v === '--silent';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    isAssert = _.some(args, function(v) {
 | 
				
			||||||
 | 
					      return v === '-a' || v === '--assert';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    isMono = _.some(args, function(v) {
 | 
				
			||||||
 | 
					      return v === '--no-color';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      color: !isMono,
 | 
				
			||||||
 | 
					      debug: isDebug,
 | 
				
			||||||
 | 
					      silent: isSilent,
 | 
				
			||||||
 | 
					      assert: isAssert,
 | 
				
			||||||
 | 
					      orgVerb: oVerb,
 | 
				
			||||||
 | 
					      verb: verb,
 | 
				
			||||||
 | 
					      json: oJSON,
 | 
				
			||||||
 | 
					      args: args
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Invoke a HackMyResume verb. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  execute = function(src, dst, opts, log) {
 | 
				
			||||||
 | 
					    var prom, v;
 | 
				
			||||||
 | 
					    v = new HMR.verbs[this.name()]();
 | 
				
			||||||
 | 
					    loadOptions.call(this, opts, this.parent.jsonArgs);
 | 
				
			||||||
 | 
					    _opts.errHandler = v;
 | 
				
			||||||
 | 
					    _out.init(_opts);
 | 
				
			||||||
 | 
					    v.on('hmr:status', function() {
 | 
				
			||||||
 | 
					      return _out["do"].apply(_out, arguments);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    v.on('hmr:error', function() {
 | 
				
			||||||
 | 
					      return _err.err.apply(_err, arguments);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    prom = v.invoke.call(v, src, dst, _opts, log);
 | 
				
			||||||
 | 
					    prom.then(executeSuccess, executeFail);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Success handler for verb invocations. Calls process.exit by default */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  executeSuccess = function(obj) {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Failure handler for verb invocations. Calls process.exit by default */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  executeFail = function(err) {
 | 
				
			||||||
 | 
					    var finalErrorCode, msgs;
 | 
				
			||||||
 | 
					    finalErrorCode = -1;
 | 
				
			||||||
 | 
					    if (err) {
 | 
				
			||||||
 | 
					      finalErrorCode = err.fluenterror ? err.fluenterror : err;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (_opts.debug) {
 | 
				
			||||||
 | 
					      msgs = require('./msg').errors;
 | 
				
			||||||
 | 
					      logMsg(printf(M2C(msgs.exiting.msg, 'cyan'), finalErrorCode));
 | 
				
			||||||
 | 
					      if (err.stack) {
 | 
				
			||||||
 | 
					        logMsg(err.stack);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _exitCallback(finalErrorCode);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  Initialize HackMyResume options.
 | 
				
			||||||
 | 
					  TODO: Options loading is a little hacky, for two reasons:
 | 
				
			||||||
 | 
					    - Commander.js idiosyncracies
 | 
				
			||||||
 | 
					    - Need to accept JSON inputs from the command line.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loadOptions = function(o, cmdO) {
 | 
				
			||||||
 | 
					    if (cmdO) {
 | 
				
			||||||
 | 
					      o = EXTEND(true, o, cmdO);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    o = EXTEND(true, o, this.opts());
 | 
				
			||||||
 | 
					    if (this.parent.silent !== void 0 && this.parent.silent !== null) {
 | 
				
			||||||
 | 
					      o.silent = this.parent.silent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.parent.debug !== void 0 && this.parent.debug !== null) {
 | 
				
			||||||
 | 
					      o.debug = this.parent.debug;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.parent.assert !== void 0 && this.parent.assert !== null) {
 | 
				
			||||||
 | 
					      o.assert = this.parent.assert;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (o.debug) {
 | 
				
			||||||
 | 
					      logMsg(chalk.cyan('OPTIONS:') + '\n');
 | 
				
			||||||
 | 
					      _.each(o, function(val, key) {
 | 
				
			||||||
 | 
					        return logMsg(chalk.cyan('  %s') + chalk.cyan.bold(' %s'), PAD(key, 22, null, PAD.RIGHT), val);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      logMsg('');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    EXTEND(true, _opts, o);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Split multiple command-line filenames by the 'TO' keyword */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  splitSrcDest = function() {
 | 
				
			||||||
 | 
					    var params, splitAt;
 | 
				
			||||||
 | 
					    params = this.parent.args.filter(function(j) {
 | 
				
			||||||
 | 
					      return String.is(j);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (params.length === 0) {
 | 
				
			||||||
 | 
					      throw {
 | 
				
			||||||
 | 
					        fluenterror: HMSTATUS.resumeNotFound,
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    splitAt = _.findIndex(params, function(p) {
 | 
				
			||||||
 | 
					      return p.toLowerCase() === 'to';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (splitAt === params.length - 1 && splitAt !== -1) {
 | 
				
			||||||
 | 
					      logMsg(chalk.yellow('Please ') + chalk.yellow.bold('specify an output file') + chalk.yellow(' for this operation or ') + chalk.yellow.bold('omit the TO keyword') + chalk.yellow('.'));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      src: params.slice(0, splitAt === -1 ? void 0 : splitAt),
 | 
				
			||||||
 | 
					      dst: splitAt === -1 ? [] : params.slice(splitAt + 1)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Simple logging placeholder. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  logMsg = function() {
 | 
				
			||||||
 | 
					    return _opts.silent || console.log.apply(console.log, arguments);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=main.js.map
 | 
				
			||||||
							
								
								
									
										19
									
								
								dist/cli/msg.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								dist/cli/msg.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Message-handling routines for HackMyResume.
 | 
				
			||||||
 | 
					@module cli/msg
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var PATH, YAML;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  YAML = require('yamljs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = YAML.load(PATH.join(__dirname, 'msg.yml'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=msg.js.map
 | 
				
			||||||
							
								
								
									
										111
									
								
								dist/cli/msg.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								dist/cli/msg.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					events:
 | 
				
			||||||
 | 
					  begin:
 | 
				
			||||||
 | 
					    msg: Invoking **%s** command.
 | 
				
			||||||
 | 
					  beforeCreate:
 | 
				
			||||||
 | 
					    msg: Creating new **%s** resume: **%s**
 | 
				
			||||||
 | 
					  afterCreate:
 | 
				
			||||||
 | 
					    msg: Creating new **%s** resume: **%s**
 | 
				
			||||||
 | 
					  afterRead:
 | 
				
			||||||
 | 
					    msg: Reading **%s** resume: **%s**
 | 
				
			||||||
 | 
					  beforeTheme:
 | 
				
			||||||
 | 
					    msg: Verifying **%s** theme.
 | 
				
			||||||
 | 
					  afterTheme:
 | 
				
			||||||
 | 
					    msg: Verifying outputs:  ???
 | 
				
			||||||
 | 
					  beforeMerge:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - "Merging **%s**"
 | 
				
			||||||
 | 
					      - " onto **%s**"
 | 
				
			||||||
 | 
					  applyTheme:
 | 
				
			||||||
 | 
					    msg: Applying **%s** theme (**%s** format%s)
 | 
				
			||||||
 | 
					  afterBuild:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - "The **%s** theme says:"
 | 
				
			||||||
 | 
					      - |
 | 
				
			||||||
 | 
					          "For best results view JSON Resume themes over a
 | 
				
			||||||
 | 
					          local or remote HTTP connection. For example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            npm install http-server -g
 | 
				
			||||||
 | 
					            http-server <resume-folder>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          For more information, see the README."
 | 
				
			||||||
 | 
					  afterGenerate:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - " (with %s)"
 | 
				
			||||||
 | 
					      - "Skipping %s resume: %s"
 | 
				
			||||||
 | 
					      - "Generating **%s** resume: **%s**"
 | 
				
			||||||
 | 
					  beforeAnalyze:
 | 
				
			||||||
 | 
					    msg: "Analyzing **%s** resume: **%s**"
 | 
				
			||||||
 | 
					  beforeConvert:
 | 
				
			||||||
 | 
					    msg: "Converting **%s** (**%s**) to **%s** (**%s**)"
 | 
				
			||||||
 | 
					  afterValidate:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - "Validating **%s** against the **%s** schema: "
 | 
				
			||||||
 | 
					      - "VALID!"
 | 
				
			||||||
 | 
					      - "INVALID"
 | 
				
			||||||
 | 
					      - "BROKEN"
 | 
				
			||||||
 | 
					      - "MISSING"
 | 
				
			||||||
 | 
					      - "ERROR"
 | 
				
			||||||
 | 
					  beforePeek:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - Peeking at **%s** in **%s**
 | 
				
			||||||
 | 
					      - Peeking at **%s**
 | 
				
			||||||
 | 
					  afterPeek:
 | 
				
			||||||
 | 
					    msg: "The specified key **%s** was not found in **%s**."
 | 
				
			||||||
 | 
					  afterInlineConvert:
 | 
				
			||||||
 | 
					    msg: Converting **%s** to **%s** format.
 | 
				
			||||||
 | 
					errors:
 | 
				
			||||||
 | 
					  themeNotFound:
 | 
				
			||||||
 | 
					    msg: >
 | 
				
			||||||
 | 
					      **Couldn't find the '%s' theme.** Please specify the name of a preinstalled
 | 
				
			||||||
 | 
					      FRESH theme or the path to a locally installed FRESH or JSON Resume theme.
 | 
				
			||||||
 | 
					  copyCSS:
 | 
				
			||||||
 | 
					    msg: Couldn't copy CSS file to destination folder.
 | 
				
			||||||
 | 
					  resumeNotFound:
 | 
				
			||||||
 | 
					    msg: Please **feed me a resume** in FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					  missingCommand:
 | 
				
			||||||
 | 
					    msg: Please **give me a command**
 | 
				
			||||||
 | 
					  invalidCommand:
 | 
				
			||||||
 | 
					    msg: Invalid command: '%s'
 | 
				
			||||||
 | 
					  resumeNotFoundAlt:
 | 
				
			||||||
 | 
					    msg: Please **feed me a resume** in either FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					  inputOutputParity:
 | 
				
			||||||
 | 
					    msg: Please **specify an output file name** for every input file you wish to convert.
 | 
				
			||||||
 | 
					  createNameMissing:
 | 
				
			||||||
 | 
					    msg: Please **specify the filename** of the resume to create.
 | 
				
			||||||
 | 
					  pdfGeneration:
 | 
				
			||||||
 | 
					    msg: PDF generation failed. Make sure wkhtmltopdf is installed and accessible from your path.
 | 
				
			||||||
 | 
					  invalid:
 | 
				
			||||||
 | 
					    msg: Validation failed and the --assert option was specified.
 | 
				
			||||||
 | 
					  invalidFormat:
 | 
				
			||||||
 | 
					    msg: The **%s** theme doesn't support the **%s** format.
 | 
				
			||||||
 | 
					  notOnPath:
 | 
				
			||||||
 | 
					    msg: %s wasn't found on your system path or is inaccessible. PDF not generated.
 | 
				
			||||||
 | 
					  readError:
 | 
				
			||||||
 | 
					    msg: Reading **???** resume: **%s**
 | 
				
			||||||
 | 
					  parseError:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - Invalid or corrupt JSON on line %s column %s.
 | 
				
			||||||
 | 
					      - Invalid or corrupt JSON on line %s.
 | 
				
			||||||
 | 
					      - Invalid or corrupt JSON.
 | 
				
			||||||
 | 
					  invalidHelperUse:
 | 
				
			||||||
 | 
					    msg: "**Warning**: Incorrect use of the **%s** theme helper."
 | 
				
			||||||
 | 
					  fileSaveError:
 | 
				
			||||||
 | 
					    msg: An error occurred while writing %s to disk: %s.
 | 
				
			||||||
 | 
					  mixedMerge:
 | 
				
			||||||
 | 
					    msg: "**Warning:** merging mixed resume types. Errors may occur."
 | 
				
			||||||
 | 
					  invokeTemplate:
 | 
				
			||||||
 | 
					    msg: "An error occurred during template invocation."
 | 
				
			||||||
 | 
					  compileTemplate:
 | 
				
			||||||
 | 
					    msg: "An error occurred during template compilation."
 | 
				
			||||||
 | 
					  themeLoad:
 | 
				
			||||||
 | 
					    msg: "Applying **%s** theme (? formats)"
 | 
				
			||||||
 | 
					  invalidParamCount:
 | 
				
			||||||
 | 
					    msg: "Invalid number of parameters. Expected: **%s**."
 | 
				
			||||||
 | 
					  missingParam:
 | 
				
			||||||
 | 
					    msg: The '**%s**' parameter was needed but not supplied.
 | 
				
			||||||
 | 
					  createError:
 | 
				
			||||||
 | 
					    msg: Failed to create **'%s'**.
 | 
				
			||||||
 | 
					  exiting:
 | 
				
			||||||
 | 
					    msg: Exiting with status code **%s**.
 | 
				
			||||||
 | 
					  validateError:
 | 
				
			||||||
 | 
					    msg: "An error occurred during validation:\n%s"
 | 
				
			||||||
							
								
								
									
										193
									
								
								dist/cli/out.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								dist/cli/out.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,193 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Output routines for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module cli/out
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var EXTEND, FS, HANDLEBARS, HME, LO, M2C, OutputHandler, PATH, YAML, _, chalk, dbgStyle, pad, printf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HME = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  M2C = require('../utils/md2chalk.js');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LO = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EXTEND = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HANDLEBARS = require('handlebars');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  YAML = require('yamljs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  printf = require('printf');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pad = require('string-padding');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dbgStyle = 'cyan';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** A stateful output module. All HMR console output handled here. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = OutputHandler = (function() {
 | 
				
			||||||
 | 
					    function OutputHandler(opts) {
 | 
				
			||||||
 | 
					      this.init(opts);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    OutputHandler.prototype.init = function(opts) {
 | 
				
			||||||
 | 
					      this.opts = EXTEND(true, this.opts || {}, opts);
 | 
				
			||||||
 | 
					      this.msgs = YAML.load(PATH.join(__dirname, 'msg.yml')).events;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    OutputHandler.prototype.log = function(msg) {
 | 
				
			||||||
 | 
					      var finished;
 | 
				
			||||||
 | 
					      msg = msg || '';
 | 
				
			||||||
 | 
					      printf = require('printf');
 | 
				
			||||||
 | 
					      finished = printf.apply(printf, arguments);
 | 
				
			||||||
 | 
					      return this.opts.silent || console.log(finished);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    OutputHandler.prototype["do"] = function(evt) {
 | 
				
			||||||
 | 
					      var L, WRAP, adj, info, msg, msgs, numFormats, output, rawTpl, sty, style, suffix, template, that, themeName, tot;
 | 
				
			||||||
 | 
					      that = this;
 | 
				
			||||||
 | 
					      L = function() {
 | 
				
			||||||
 | 
					        return that.log.apply(that, arguments);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      switch (evt.sub) {
 | 
				
			||||||
 | 
					        case HME.begin:
 | 
				
			||||||
 | 
					          return this.opts.debug && L(M2C(this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase());
 | 
				
			||||||
 | 
					        case HME.afterCreate:
 | 
				
			||||||
 | 
					          L(M2C(this.msgs.beforeCreate.msg, evt.isError ? 'red' : 'green'), evt.fmt, evt.file);
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case HME.beforeTheme:
 | 
				
			||||||
 | 
					          return this.opts.debug && L(M2C(this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase());
 | 
				
			||||||
 | 
					        case HME.afterParse:
 | 
				
			||||||
 | 
					          return L(M2C(this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file);
 | 
				
			||||||
 | 
					        case HME.beforeMerge:
 | 
				
			||||||
 | 
					          msg = '';
 | 
				
			||||||
 | 
					          evt.f.reverse().forEach(function(a, idx) {
 | 
				
			||||||
 | 
					            return msg += printf((idx === 0 ? this.msgs.beforeMerge.msg[0] : this.msgs.beforeMerge.msg[1]), a.file);
 | 
				
			||||||
 | 
					          }, this);
 | 
				
			||||||
 | 
					          return L(M2C(msg, (evt.mixed ? 'yellow' : 'gray'), 'white.dim'));
 | 
				
			||||||
 | 
					        case HME.applyTheme:
 | 
				
			||||||
 | 
					          this.theme = evt.theme;
 | 
				
			||||||
 | 
					          numFormats = Object.keys(evt.theme.formats).length;
 | 
				
			||||||
 | 
					          return L(M2C(this.msgs.applyTheme.msg, evt.status === 'error' ? 'red' : 'gray', evt.status === 'error' ? 'bold' : 'white.dim'), evt.theme.name.toUpperCase(), numFormats, numFormats === 1 ? '' : 's');
 | 
				
			||||||
 | 
					        case HME.end:
 | 
				
			||||||
 | 
					          if (evt.cmd === 'build') {
 | 
				
			||||||
 | 
					            themeName = this.theme.name.toUpperCase();
 | 
				
			||||||
 | 
					            if (this.opts.tips && (this.theme.message || this.theme.render)) {
 | 
				
			||||||
 | 
					              WRAP = require('word-wrap');
 | 
				
			||||||
 | 
					              if (this.theme.message) {
 | 
				
			||||||
 | 
					                L(M2C(this.msgs.afterBuild.msg[0], 'cyan'), themeName);
 | 
				
			||||||
 | 
					                return L(M2C(this.theme.message, 'white'));
 | 
				
			||||||
 | 
					              } else if (this.theme.render) {
 | 
				
			||||||
 | 
					                L(M2C(this.msgs.afterBuild.msg[0], 'cyan'), themeName);
 | 
				
			||||||
 | 
					                return L(M2C(this.msgs.afterBuild.msg[1], 'white'));
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case HME.afterGenerate:
 | 
				
			||||||
 | 
					          suffix = '';
 | 
				
			||||||
 | 
					          if (evt.fmt === 'pdf') {
 | 
				
			||||||
 | 
					            if (this.opts.pdf) {
 | 
				
			||||||
 | 
					              if (this.opts.pdf !== 'none') {
 | 
				
			||||||
 | 
					                suffix = printf(M2C(this.msgs.afterGenerate.msg[0], evt.error ? 'red' : 'green'), this.opts.pdf);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                L(M2C(this.msgs.afterGenerate.msg[1], 'gray'), evt.fmt.toUpperCase(), evt.file);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return L(M2C(this.msgs.afterGenerate.msg[2] + suffix, evt.error ? 'red' : 'green'), pad(evt.fmt.toUpperCase(), 4, null, pad.RIGHT), PATH.relative(process.cwd(), evt.file));
 | 
				
			||||||
 | 
					        case HME.beforeAnalyze:
 | 
				
			||||||
 | 
					          return L(M2C(this.msgs.beforeAnalyze.msg, 'green'), evt.fmt, evt.file);
 | 
				
			||||||
 | 
					        case HME.afterAnalyze:
 | 
				
			||||||
 | 
					          info = evt.info;
 | 
				
			||||||
 | 
					          rawTpl = FS.readFileSync(PATH.join(__dirname, 'analyze.hbs'), 'utf8');
 | 
				
			||||||
 | 
					          HANDLEBARS.registerHelper(require('../helpers/console-helpers'));
 | 
				
			||||||
 | 
					          template = HANDLEBARS.compile(rawTpl, {
 | 
				
			||||||
 | 
					            strict: false,
 | 
				
			||||||
 | 
					            assumeObjects: false
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          tot = 0;
 | 
				
			||||||
 | 
					          info.keywords.forEach(function(g) {
 | 
				
			||||||
 | 
					            return tot += g.count;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          info.keywords.totalKeywords = tot;
 | 
				
			||||||
 | 
					          output = template(info);
 | 
				
			||||||
 | 
					          return this.log(chalk.cyan(output));
 | 
				
			||||||
 | 
					        case HME.beforeConvert:
 | 
				
			||||||
 | 
					          return L(M2C(this.msgs.beforeConvert.msg, 'green'), evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt);
 | 
				
			||||||
 | 
					        case HME.afterInlineConvert:
 | 
				
			||||||
 | 
					          return L(M2C(this.msgs.afterInlineConvert.msg, 'gray', 'white.dim'), evt.file, evt.fmt);
 | 
				
			||||||
 | 
					        case HME.afterValidate:
 | 
				
			||||||
 | 
					          style = 'red';
 | 
				
			||||||
 | 
					          adj = '';
 | 
				
			||||||
 | 
					          msgs = this.msgs.afterValidate.msg;
 | 
				
			||||||
 | 
					          switch (evt.status) {
 | 
				
			||||||
 | 
					            case 'valid':
 | 
				
			||||||
 | 
					              style = 'green';
 | 
				
			||||||
 | 
					              adj = msgs[1];
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case 'invalid':
 | 
				
			||||||
 | 
					              style = 'yellow';
 | 
				
			||||||
 | 
					              adj = msgs[2];
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case 'broken':
 | 
				
			||||||
 | 
					              style = 'red';
 | 
				
			||||||
 | 
					              adj = msgs[3];
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case 'missing':
 | 
				
			||||||
 | 
					              style = 'red';
 | 
				
			||||||
 | 
					              adj = msgs[4];
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case 'unknown':
 | 
				
			||||||
 | 
					              style = 'red';
 | 
				
			||||||
 | 
					              adj = msgs[5];
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          evt.schema = evt.schema.replace('jars', 'JSON Resume').toUpperCase();
 | 
				
			||||||
 | 
					          L(M2C(msgs[0], 'white') + chalk[style].bold(adj), evt.file, evt.schema);
 | 
				
			||||||
 | 
					          if (evt.violations) {
 | 
				
			||||||
 | 
					            _.each(evt.violations, function(err, idx) {
 | 
				
			||||||
 | 
					              L(chalk.yellow.bold('--> ') + chalk.yellow(err.field.replace('data.', 'resume.').toUpperCase() + ' ' + err.message));
 | 
				
			||||||
 | 
					            }, this);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case HME.afterPeek:
 | 
				
			||||||
 | 
					          sty = evt.error ? 'red' : (evt.target !== void 0 ? 'green' : 'yellow');
 | 
				
			||||||
 | 
					          if (evt.requested) {
 | 
				
			||||||
 | 
					            L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (evt.target !== void 0 && !evt.error) {
 | 
				
			||||||
 | 
					            return console.dir(evt.target, {
 | 
				
			||||||
 | 
					              depth: null,
 | 
				
			||||||
 | 
					              colors: true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          } else if (!evt.error) {
 | 
				
			||||||
 | 
					            return L(M2C(this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file);
 | 
				
			||||||
 | 
					          } else if (evt.error) {
 | 
				
			||||||
 | 
					            return L(chalk.red(evt.error.inner.inner));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return OutputHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=out.js.map
 | 
				
			||||||
							
								
								
									
										51
									
								
								dist/cli/use.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								dist/cli/use.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					Usage:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hackmyresume <command> <sources> [TO <targets>] [<options>]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Available commands:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BUILD         Build your resume to the destination format(s).
 | 
				
			||||||
 | 
					  ANALYZE       Analyze your resume for keywords, gaps, and metrics.
 | 
				
			||||||
 | 
					  VALIDATE      Validate your resume for errors and typos.
 | 
				
			||||||
 | 
					  CONVERT       Convert your resume between FRESH and JSON Resume.
 | 
				
			||||||
 | 
					  NEW           Create a new resume in FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					  PEEK          View a specific field or element on your resume.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Available options:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --theme -t    Path to a FRESH or JSON Resume theme.
 | 
				
			||||||
 | 
					  --pdf -p      Specify the PDF engine to use (wkhtmltopdf or phantom).
 | 
				
			||||||
 | 
					  --options -o  Load options from an external JSON file.
 | 
				
			||||||
 | 
					  --format -f   The format (FRESH or JSON Resume) to use.
 | 
				
			||||||
 | 
					  --debug -d    Emit extended debugging info.
 | 
				
			||||||
 | 
					  --assert -a   Treat resume validation warnings as errors.
 | 
				
			||||||
 | 
					  --no-colors   Disable terminal colors.
 | 
				
			||||||
 | 
					  --tips        Display theme messages and tips.
 | 
				
			||||||
 | 
					  --help -h     Display help documentation.
 | 
				
			||||||
 | 
					  --version -v  Display the current version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Not all options are supported for all commands. For example, the
 | 
				
			||||||
 | 
					--theme option is only supported for the BUILD command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Examples:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hackmyresume  BUILD resume.json TO out/resume.all --theme modern
 | 
				
			||||||
 | 
					  hackmyresume  ANALYZE resume.json
 | 
				
			||||||
 | 
					  hackmyresume  NEW my-new-resume.json --format JRS
 | 
				
			||||||
 | 
					  hackmyresume  CONVERT resume-fresh.json TO resume-jrs.json
 | 
				
			||||||
 | 
					  hackmyresume  VALIDATE resume.json
 | 
				
			||||||
 | 
					  hackmyresume  PEEK resume.json employment[2].summary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tips:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - You can specify multiple sources and/or targets for all commands.
 | 
				
			||||||
 | 
					  - You can use any FRESH or JSON Resume theme with HackMyResume.
 | 
				
			||||||
 | 
					  - Specify a file extension of .all to generate your resume to all
 | 
				
			||||||
 | 
					    available formats supported by the theme. (BUILD command.)
 | 
				
			||||||
 | 
					  - The --theme parameter can specify either the name of a preinstalled
 | 
				
			||||||
 | 
					    theme, or the path to a local FRESH or JSON Resume theme.
 | 
				
			||||||
 | 
					  - Visit https://www.npmjs.com/search?q=jsonresume-theme for a full
 | 
				
			||||||
 | 
					    listing of all available JSON Resume themes.
 | 
				
			||||||
 | 
					  - Visit https://github.com/fluentdesk/fresh-themes for a complete
 | 
				
			||||||
 | 
					    listing of all available FRESH themes.
 | 
				
			||||||
 | 
					  - Report bugs to https://githut.com/hacksalot/HackMyResume/issues.
 | 
				
			||||||
							
								
								
									
										73
									
								
								dist/core/abstract-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								dist/core/abstract-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the AbstractResume class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/abstract-resume
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var AbstractResume, FluentDate, _, __;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  __ = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = require('./fluent-date');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AbstractResume = (function() {
 | 
				
			||||||
 | 
					    function AbstractResume() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Compute the total duration of the work history.
 | 
				
			||||||
 | 
					    @returns The total duration of the sheet's work history, that is, the number
 | 
				
			||||||
 | 
					    of years between the start date of the earliest job on the resume and the
 | 
				
			||||||
 | 
					    *latest end date of all jobs in the work history*. This last condition is for
 | 
				
			||||||
 | 
					    sheets that have overlapping jobs.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AbstractResume.prototype.duration = function(collKey, startKey, endKey, unit) {
 | 
				
			||||||
 | 
					      var firstDate, hist, lastDate, new_e;
 | 
				
			||||||
 | 
					      unit = unit || 'years';
 | 
				
			||||||
 | 
					      hist = __.get(this, collKey);
 | 
				
			||||||
 | 
					      if (!hist || !hist.length) {
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      new_e = hist.map(function(job) {
 | 
				
			||||||
 | 
					        var obj;
 | 
				
			||||||
 | 
					        obj = _.pick(job, [startKey, endKey]);
 | 
				
			||||||
 | 
					        if (!_.has(obj, endKey)) {
 | 
				
			||||||
 | 
					          obj[endKey] = 'current';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (obj && (obj[startKey] || obj[endKey])) {
 | 
				
			||||||
 | 
					          obj = _.pairs(obj);
 | 
				
			||||||
 | 
					          obj[0][1] = FluentDate.fmt(obj[0][1]);
 | 
				
			||||||
 | 
					          if (obj.length > 1) {
 | 
				
			||||||
 | 
					            obj[1][1] = FluentDate.fmt(obj[1][1]);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return obj;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      new_e = _.filter(_.flatten(new_e, true), function(v) {
 | 
				
			||||||
 | 
					        return v && v.length && v[0] && v[0].length;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (!new_e || !new_e.length) {
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      new_e = _.sortBy(new_e, function(elem) {
 | 
				
			||||||
 | 
					        return elem[1].unix();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      firstDate = _.first(new_e)[1];
 | 
				
			||||||
 | 
					      lastDate = _.last(new_e)[1];
 | 
				
			||||||
 | 
					      return lastDate.diff(firstDate, unit);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AbstractResume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = AbstractResume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=abstract-resume.js.map
 | 
				
			||||||
							
								
								
									
										62
									
								
								dist/core/default-formats.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								dist/core/default-formats.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Event code definitions.
 | 
				
			||||||
 | 
					@module core/default-formats
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Supported resume formats. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: 'html',
 | 
				
			||||||
 | 
					      ext: 'html',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/html-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'txt',
 | 
				
			||||||
 | 
					      ext: 'txt',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/text-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'doc',
 | 
				
			||||||
 | 
					      ext: 'doc',
 | 
				
			||||||
 | 
					      fmt: 'xml',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/word-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'pdf',
 | 
				
			||||||
 | 
					      ext: 'pdf',
 | 
				
			||||||
 | 
					      fmt: 'html',
 | 
				
			||||||
 | 
					      is: false,
 | 
				
			||||||
 | 
					      gen: new (require('../generators/html-pdf-cli-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'png',
 | 
				
			||||||
 | 
					      ext: 'png',
 | 
				
			||||||
 | 
					      fmt: 'html',
 | 
				
			||||||
 | 
					      is: false,
 | 
				
			||||||
 | 
					      gen: new (require('../generators/html-png-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'md',
 | 
				
			||||||
 | 
					      ext: 'md',
 | 
				
			||||||
 | 
					      fmt: 'txt',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/markdown-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'json',
 | 
				
			||||||
 | 
					      ext: 'json',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/json-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'yml',
 | 
				
			||||||
 | 
					      ext: 'yml',
 | 
				
			||||||
 | 
					      fmt: 'yml',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/json-yaml-generator'))()
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					      name: 'latex',
 | 
				
			||||||
 | 
					      ext: 'tex',
 | 
				
			||||||
 | 
					      fmt: 'latex',
 | 
				
			||||||
 | 
					      gen: new (require('../generators/latex-generator'))()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=default-formats.js.map
 | 
				
			||||||
							
								
								
									
										20
									
								
								dist/core/default-options.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								dist/core/default-options.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Event code definitions.
 | 
				
			||||||
 | 
					@module core/default-options
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = {
 | 
				
			||||||
 | 
					    theme: 'modern',
 | 
				
			||||||
 | 
					    prettify: {
 | 
				
			||||||
 | 
					      indent_size: 2,
 | 
				
			||||||
 | 
					      unformatted: ['em', 'strong'],
 | 
				
			||||||
 | 
					      max_char: 80
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=default-options.js.map
 | 
				
			||||||
							
								
								
									
										44
									
								
								dist/core/event-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								dist/core/event-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Event code definitions.
 | 
				
			||||||
 | 
					@module core/event-codes
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = {
 | 
				
			||||||
 | 
					    error: -1,
 | 
				
			||||||
 | 
					    success: 0,
 | 
				
			||||||
 | 
					    begin: 1,
 | 
				
			||||||
 | 
					    end: 2,
 | 
				
			||||||
 | 
					    beforeRead: 3,
 | 
				
			||||||
 | 
					    afterRead: 4,
 | 
				
			||||||
 | 
					    beforeCreate: 5,
 | 
				
			||||||
 | 
					    afterCreate: 6,
 | 
				
			||||||
 | 
					    beforeTheme: 7,
 | 
				
			||||||
 | 
					    afterTheme: 8,
 | 
				
			||||||
 | 
					    beforeMerge: 9,
 | 
				
			||||||
 | 
					    afterMerge: 10,
 | 
				
			||||||
 | 
					    beforeGenerate: 11,
 | 
				
			||||||
 | 
					    afterGenerate: 12,
 | 
				
			||||||
 | 
					    beforeAnalyze: 13,
 | 
				
			||||||
 | 
					    afterAnalyze: 14,
 | 
				
			||||||
 | 
					    beforeConvert: 15,
 | 
				
			||||||
 | 
					    afterConvert: 16,
 | 
				
			||||||
 | 
					    verifyOutputs: 17,
 | 
				
			||||||
 | 
					    beforeParse: 18,
 | 
				
			||||||
 | 
					    afterParse: 19,
 | 
				
			||||||
 | 
					    beforePeek: 20,
 | 
				
			||||||
 | 
					    afterPeek: 21,
 | 
				
			||||||
 | 
					    beforeInlineConvert: 22,
 | 
				
			||||||
 | 
					    afterInlineConvert: 23,
 | 
				
			||||||
 | 
					    beforeValidate: 24,
 | 
				
			||||||
 | 
					    afterValidate: 25,
 | 
				
			||||||
 | 
					    beforeWrite: 26,
 | 
				
			||||||
 | 
					    afterWrite: 27,
 | 
				
			||||||
 | 
					    applyTheme: 28
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=event-codes.js.map
 | 
				
			||||||
							
								
								
									
										107
									
								
								dist/core/fluent-date.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								dist/core/fluent-date.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					The HackMyResume date representation.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/fluent-date
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FluentDate, abbr, moment, months;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('../utils/string');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Create a FluentDate from a string or Moment date object. There are a few date
 | 
				
			||||||
 | 
					  formats to be aware of here.
 | 
				
			||||||
 | 
					  1. The words "Present" and "Now", referring to the current date
 | 
				
			||||||
 | 
					  2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
 | 
				
			||||||
 | 
					  3. Year-and-month only ("2015-04")
 | 
				
			||||||
 | 
					  4. Year-only "YYYY" ("2015")
 | 
				
			||||||
 | 
					  5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008")
 | 
				
			||||||
 | 
					  6. Empty dates ("", " ")
 | 
				
			||||||
 | 
					  7. Any other date format that Moment.js can parse from
 | 
				
			||||||
 | 
					  Note: Moment can transparently parse all or most of these, without requiring us
 | 
				
			||||||
 | 
					  to specify a date format...but for maximum parsing safety and to avoid Moment
 | 
				
			||||||
 | 
					  deprecation warnings, it's recommended to either a) explicitly specify the date
 | 
				
			||||||
 | 
					  format or b) use an ISO format. For clarity, we handle these cases explicitly.
 | 
				
			||||||
 | 
					  @class FluentDate
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = (function() {
 | 
				
			||||||
 | 
					    function FluentDate(dt) {
 | 
				
			||||||
 | 
					      this.rep = this.fmt(dt);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FluentDate.isCurrent = function(dt) {
 | 
				
			||||||
 | 
					      return !dt || (String.is(dt) && /^(present|now|current)$/.test(dt));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return FluentDate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  months = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  abbr = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment.months().forEach(function(m, idx) {
 | 
				
			||||||
 | 
					    return months[m.toLowerCase()] = idx + 1;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment.monthsShort().forEach(function(m, idx) {
 | 
				
			||||||
 | 
					    return abbr[m.toLowerCase()] = idx + 1;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  abbr.sept = 9;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = FluentDate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate.fmt = function(dt, throws) {
 | 
				
			||||||
 | 
					    var month, mt, parts, ref, temp;
 | 
				
			||||||
 | 
					    throws = (throws === void 0 || throws === null) || throws;
 | 
				
			||||||
 | 
					    if (typeof dt === 'string' || dt instanceof String) {
 | 
				
			||||||
 | 
					      dt = dt.toLowerCase().trim();
 | 
				
			||||||
 | 
					      if (/^(present|now|current)$/.test(dt)) {
 | 
				
			||||||
 | 
					        return moment();
 | 
				
			||||||
 | 
					      } else if (/^\D+\s+\d{4}$/.test(dt)) {
 | 
				
			||||||
 | 
					        parts = dt.split(' ');
 | 
				
			||||||
 | 
					        month = months[parts[0]] || abbr[parts[0]];
 | 
				
			||||||
 | 
					        temp = parts[1] + '-' + ((ref = month < 10) != null ? ref : '0' + {
 | 
				
			||||||
 | 
					          month: month.toString()
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return moment(temp, 'YYYY-MM');
 | 
				
			||||||
 | 
					      } else if (/^\d{4}-\d{1,2}$/.test(dt)) {
 | 
				
			||||||
 | 
					        return moment(dt, 'YYYY-MM');
 | 
				
			||||||
 | 
					      } else if (/^\s*\d{4}\s*$/.test(dt)) {
 | 
				
			||||||
 | 
					        return moment(dt, 'YYYY');
 | 
				
			||||||
 | 
					      } else if (/^\s*$/.test(dt)) {
 | 
				
			||||||
 | 
					        return moment();
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        mt = moment(dt);
 | 
				
			||||||
 | 
					        if (mt.isValid()) {
 | 
				
			||||||
 | 
					          return mt;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (throws) {
 | 
				
			||||||
 | 
					          throw 'Invalid date format encountered.';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (!dt) {
 | 
				
			||||||
 | 
					        return moment();
 | 
				
			||||||
 | 
					      } else if (dt.isValid && dt.isValid()) {
 | 
				
			||||||
 | 
					        return dt;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (throws) {
 | 
				
			||||||
 | 
					        throw 'Unknown date object encountered.';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=fluent-date.js.map
 | 
				
			||||||
							
								
								
									
										510
									
								
								dist/core/fresh-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										510
									
								
								dist/core/fresh-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,510 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the FRESHResume class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/fresh-resume
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var AbstractResume, CONVERTER, FS, FluentDate, FreshResume, JRSResume, MD, PATH, XML, _, __, _parseDates, extend, moment, validator,
 | 
				
			||||||
 | 
					    extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extend = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validator = require('is-my-json-valid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  __ = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  XML = require('xml-escape');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CONVERTER = require('fresh-jrs-converter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSResume = require('./jrs-resume');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = require('./fluent-date');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AbstractResume = require('./abstract-resume');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
 | 
				
			||||||
 | 
					  object is an instantiation of that JSON decorated with utility methods.
 | 
				
			||||||
 | 
					  @constructor
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FreshResume = (function(superClass) {
 | 
				
			||||||
 | 
					    extend1(FreshResume, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function FreshResume() {
 | 
				
			||||||
 | 
					      return FreshResume.__super__.constructor.apply(this, arguments);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Initialize the the FreshResume from JSON string data. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.parse = function(stringData, opts) {
 | 
				
			||||||
 | 
					      var ref;
 | 
				
			||||||
 | 
					      this.imp = (ref = this.imp) != null ? ref : {
 | 
				
			||||||
 | 
					        raw: stringData
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return this.parseJSON(JSON.parse(stringData), opts);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Initialize the FreshResume from JSON.
 | 
				
			||||||
 | 
					    Open and parse the specified FRESH resume. Merge the JSON object model onto
 | 
				
			||||||
 | 
					    this Sheet instance with extend() and convert sheet dates to a safe &
 | 
				
			||||||
 | 
					    consistent format. Then sort each section by startDate descending.
 | 
				
			||||||
 | 
					    @param rep {Object} The raw JSON representation.
 | 
				
			||||||
 | 
					    @param opts {Object} Resume loading and parsing options.
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      date: Perform safe date conversion.
 | 
				
			||||||
 | 
					      sort: Sort resume items by date.
 | 
				
			||||||
 | 
					      compute: Prepare computed resume totals.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.parseJSON = function(rep, opts) {
 | 
				
			||||||
 | 
					      var ignoreList, ref, scrubbed, that, traverse;
 | 
				
			||||||
 | 
					      that = this;
 | 
				
			||||||
 | 
					      traverse = require('traverse');
 | 
				
			||||||
 | 
					      ignoreList = [];
 | 
				
			||||||
 | 
					      scrubbed = traverse(rep).map(function(x) {
 | 
				
			||||||
 | 
					        if (!this.isLeaf && this.node.ignore) {
 | 
				
			||||||
 | 
					          if (this.node.ignore === true || this.node.ignore === 'true') {
 | 
				
			||||||
 | 
					            ignoreList.push(this.node);
 | 
				
			||||||
 | 
					            return this.remove();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      extend(true, this, scrubbed);
 | 
				
			||||||
 | 
					      if (!((ref = this.imp) != null ? ref.processed : void 0)) {
 | 
				
			||||||
 | 
					        opts = opts || {};
 | 
				
			||||||
 | 
					        if (opts.imp === void 0 || opts.imp) {
 | 
				
			||||||
 | 
					          this.imp = this.imp || {};
 | 
				
			||||||
 | 
					          this.imp.title = (opts.title || this.imp.title) || this.name;
 | 
				
			||||||
 | 
					          if (!this.imp.raw) {
 | 
				
			||||||
 | 
					            this.imp.raw = JSON.stringify(rep);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.imp.processed = true;
 | 
				
			||||||
 | 
					        (opts.date === void 0 || opts.date) && _parseDates.call(this);
 | 
				
			||||||
 | 
					        (opts.sort === void 0 || opts.sort) && this.sort();
 | 
				
			||||||
 | 
					        (opts.compute === void 0 || opts.compute) && (this.computed = {
 | 
				
			||||||
 | 
					          numYears: this.duration(),
 | 
				
			||||||
 | 
					          keywords: this.keywords()
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Save the sheet to disk (for environments that have disk access). */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.save = function(filename) {
 | 
				
			||||||
 | 
					      this.imp.file = filename || this.imp.file;
 | 
				
			||||||
 | 
					      FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Save the sheet to disk in a specific format, either FRESH or JSON Resume.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.saveAs = function(filename, format) {
 | 
				
			||||||
 | 
					      var newRep;
 | 
				
			||||||
 | 
					      if (format !== 'JRS') {
 | 
				
			||||||
 | 
					        this.imp.file = filename || this.imp.file;
 | 
				
			||||||
 | 
					        FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        newRep = CONVERTER.toJRS(this);
 | 
				
			||||||
 | 
					        FS.writeFileSync(filename, JRSResume.stringify(newRep), 'utf8');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Duplicate this FreshResume instance.
 | 
				
			||||||
 | 
					    This method first extend()s this object onto an empty, creating a deep copy,
 | 
				
			||||||
 | 
					    and then passes the result into a new FreshResume instance via .parseJSON.
 | 
				
			||||||
 | 
					    We do it this way to create a true clone of the object without re-running any
 | 
				
			||||||
 | 
					    of the associated processing.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.dupe = function() {
 | 
				
			||||||
 | 
					      var jso, rnew;
 | 
				
			||||||
 | 
					      jso = extend(true, {}, this);
 | 
				
			||||||
 | 
					      rnew = new FreshResume();
 | 
				
			||||||
 | 
					      rnew.parseJSON(jso, {});
 | 
				
			||||||
 | 
					      return rnew;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Convert this object to a JSON string, sanitizing meta-properties along the
 | 
				
			||||||
 | 
					    way.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.stringify = function() {
 | 
				
			||||||
 | 
					      return FreshResume.stringify(this);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Create a copy of this resume in which all string fields have been run through
 | 
				
			||||||
 | 
					    a transformation function (such as a Markdown filter or XML encoder).
 | 
				
			||||||
 | 
					    TODO: Move this out of FRESHResume.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.transformStrings = function(filt, transformer) {
 | 
				
			||||||
 | 
					      var ret, trx;
 | 
				
			||||||
 | 
					      ret = this.dupe();
 | 
				
			||||||
 | 
					      trx = require('../utils/string-transformer');
 | 
				
			||||||
 | 
					      return trx(ret, filt, transformer);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Create a copy of this resume in which all fields have been interpreted as
 | 
				
			||||||
 | 
					    Markdown.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.markdownify = function() {
 | 
				
			||||||
 | 
					      var MDIN, trx;
 | 
				
			||||||
 | 
					      MDIN = function(txt) {
 | 
				
			||||||
 | 
					        return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      trx = function(key, val) {
 | 
				
			||||||
 | 
					        if (key === 'summary') {
 | 
				
			||||||
 | 
					          return MD(val);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return MDIN(val);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return this.transformStrings(['skills', 'url', 'start', 'end', 'date'], trx);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Create a copy of this resume in which all fields have been interpreted as
 | 
				
			||||||
 | 
					    Markdown.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.xmlify = function() {
 | 
				
			||||||
 | 
					      var trx;
 | 
				
			||||||
 | 
					      trx = function(key, val) {
 | 
				
			||||||
 | 
					        return XML(val);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return this.transformStrings([], trx);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Return the resume format. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.format = function() {
 | 
				
			||||||
 | 
					      return 'FRESH';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return internal metadata. Create if it doesn't exist.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.i = function() {
 | 
				
			||||||
 | 
					      return this.imp = this.imp || {};
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Return a unique list of all keywords across all skills. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.keywords = function() {
 | 
				
			||||||
 | 
					      var flatSkills;
 | 
				
			||||||
 | 
					      flatSkills = [];
 | 
				
			||||||
 | 
					      if (this.skills) {
 | 
				
			||||||
 | 
					        if (this.skills.sets) {
 | 
				
			||||||
 | 
					          flatSkills = this.skills.sets.map(function(sk) {
 | 
				
			||||||
 | 
					            return sk.skills;
 | 
				
			||||||
 | 
					          }).reduce(function(a, b) {
 | 
				
			||||||
 | 
					            return a.concat(b);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        } else if (this.skills.list) {
 | 
				
			||||||
 | 
					          flatSkills = flatSkills.concat(this.skills.list.map(function(sk) {
 | 
				
			||||||
 | 
					            return sk.name;
 | 
				
			||||||
 | 
					          }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        flatSkills = _.uniq(flatSkills);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return flatSkills;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Reset the sheet to an empty state. TODO: refactor/review
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.clear = function(clearMeta) {
 | 
				
			||||||
 | 
					      clearMeta = ((clearMeta === void 0) && true) || clearMeta;
 | 
				
			||||||
 | 
					      if (clearMeta) {
 | 
				
			||||||
 | 
					        delete this.imp;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      delete this.computed;
 | 
				
			||||||
 | 
					      delete this.employment;
 | 
				
			||||||
 | 
					      delete this.service;
 | 
				
			||||||
 | 
					      delete this.education;
 | 
				
			||||||
 | 
					      delete this.recognition;
 | 
				
			||||||
 | 
					      delete this.reading;
 | 
				
			||||||
 | 
					      delete this.writing;
 | 
				
			||||||
 | 
					      delete this.interests;
 | 
				
			||||||
 | 
					      delete this.skills;
 | 
				
			||||||
 | 
					      return delete this.social;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Get a safe count of the number of things in a section.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.count = function(obj) {
 | 
				
			||||||
 | 
					      if (!obj) {
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (obj.history) {
 | 
				
			||||||
 | 
					        return obj.history.length;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (obj.sets) {
 | 
				
			||||||
 | 
					        return obj.sets.length;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return obj.length || 0;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Add work experience to the sheet. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.add = function(moniker) {
 | 
				
			||||||
 | 
					      var defSheet, newObject;
 | 
				
			||||||
 | 
					      defSheet = FreshResume["default"]();
 | 
				
			||||||
 | 
					      newObject = defSheet[moniker].history ? $.extend(true, {}, defSheet[moniker].history[0]) : moniker === 'skills' ? $.extend(true, {}, defSheet.skills.sets[0]) : $.extend(true, {}, defSheet[moniker][0]);
 | 
				
			||||||
 | 
					      this[moniker] = this[moniker] || [];
 | 
				
			||||||
 | 
					      if (this[moniker].history) {
 | 
				
			||||||
 | 
					        this[moniker].history.push(newObject);
 | 
				
			||||||
 | 
					      } else if (moniker === 'skills') {
 | 
				
			||||||
 | 
					        this.skills.sets.push(newObject);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this[moniker].push(newObject);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return newObject;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Determine if the sheet includes a specific social profile (eg, GitHub).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.hasProfile = function(socialNetwork) {
 | 
				
			||||||
 | 
					      socialNetwork = socialNetwork.trim().toLowerCase();
 | 
				
			||||||
 | 
					      return this.social && _.some(this.social, function(p) {
 | 
				
			||||||
 | 
					        return p.network.trim().toLowerCase() === socialNetwork;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Return the specified network profile. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.getProfile = function(socialNetwork) {
 | 
				
			||||||
 | 
					      socialNetwork = socialNetwork.trim().toLowerCase();
 | 
				
			||||||
 | 
					      return this.social && _.find(this.social, function(sn) {
 | 
				
			||||||
 | 
					        return sn.network.trim().toLowerCase() === socialNetwork;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return an array of profiles for the specified network, for when the user
 | 
				
			||||||
 | 
					    has multiple eg. GitHub accounts.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.getProfiles = function(socialNetwork) {
 | 
				
			||||||
 | 
					      socialNetwork = socialNetwork.trim().toLowerCase();
 | 
				
			||||||
 | 
					      return this.social && _.filter(this.social, function(sn) {
 | 
				
			||||||
 | 
					        return sn.network.trim().toLowerCase() === socialNetwork;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Determine if the sheet includes a specific skill. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.hasSkill = function(skill) {
 | 
				
			||||||
 | 
					      skill = skill.trim().toLowerCase();
 | 
				
			||||||
 | 
					      return this.skills && _.some(this.skills, function(sk) {
 | 
				
			||||||
 | 
					        return sk.keywords && _.some(sk.keywords, function(kw) {
 | 
				
			||||||
 | 
					          return kw.trim().toLowerCase() === skill;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Validate the sheet against the FRESH Resume schema. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.isValid = function(info) {
 | 
				
			||||||
 | 
					      var ret, schemaObj, validate;
 | 
				
			||||||
 | 
					      schemaObj = require('fresca');
 | 
				
			||||||
 | 
					      validator = require('is-my-json-valid');
 | 
				
			||||||
 | 
					      validate = validator(schemaObj, {
 | 
				
			||||||
 | 
					        formats: {
 | 
				
			||||||
 | 
					          date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      ret = validate(this);
 | 
				
			||||||
 | 
					      if (!ret) {
 | 
				
			||||||
 | 
					        this.imp = this.imp || {};
 | 
				
			||||||
 | 
					        this.imp.validationErrors = validate.errors;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.duration = function(unit) {
 | 
				
			||||||
 | 
					      return FreshResume.__super__.duration.call(this, 'employment.history', 'start', 'end', unit);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Sort dated things on the sheet by start date descending. Assumes that dates
 | 
				
			||||||
 | 
					    on the sheet have been processed with _parseDates().
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FreshResume.prototype.sort = function() {
 | 
				
			||||||
 | 
					      var byDateDesc, sortSection;
 | 
				
			||||||
 | 
					      byDateDesc = function(a, b) {
 | 
				
			||||||
 | 
					        if (a.safe.start.isBefore(b.safe.start)) {
 | 
				
			||||||
 | 
					          return 1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          if (a.safe.start.isAfter(b.safe.start)) {
 | 
				
			||||||
 | 
					            return -1;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      sortSection = function(key) {
 | 
				
			||||||
 | 
					        var ar, datedThings;
 | 
				
			||||||
 | 
					        ar = __.get(this, key);
 | 
				
			||||||
 | 
					        if (ar && ar.length) {
 | 
				
			||||||
 | 
					          datedThings = obj.filter(function(o) {
 | 
				
			||||||
 | 
					            return o.start;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          return datedThings.sort(byDateDesc);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      sortSection('employment.history');
 | 
				
			||||||
 | 
					      sortSection('education.history');
 | 
				
			||||||
 | 
					      sortSection('service.history');
 | 
				
			||||||
 | 
					      sortSection('projects');
 | 
				
			||||||
 | 
					      return this.writing && this.writing.sort(function(a, b) {
 | 
				
			||||||
 | 
					        if (a.safe.date.isBefore(b.safe.date)) {
 | 
				
			||||||
 | 
					          return 1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return (a.safe.date.isAfter(b.safe.date) && -1) || 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return FreshResume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(AbstractResume);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Get the default (starter) sheet.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FreshResume["default"] = function() {
 | 
				
			||||||
 | 
					    return new FreshResume().parseJSON(require('fresh-resume-starter').fresh);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
 | 
				
			||||||
 | 
					  along the way.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FreshResume.stringify = function(obj) {
 | 
				
			||||||
 | 
					    var replacer;
 | 
				
			||||||
 | 
					    replacer = function(key, value) {
 | 
				
			||||||
 | 
					      var exKeys;
 | 
				
			||||||
 | 
					      exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'];
 | 
				
			||||||
 | 
					      if (_.some(exKeys, function(val) {
 | 
				
			||||||
 | 
					        return key.trim() === val;
 | 
				
			||||||
 | 
					      })) {
 | 
				
			||||||
 | 
					        return void 0;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return JSON.stringify(obj, replacer, 2);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Convert human-friendly dates into formal Moment.js dates for all collections.
 | 
				
			||||||
 | 
					  We don't want to lose the raw textual date as entered by the user, so we store
 | 
				
			||||||
 | 
					  the Moment-ified date as a separate property with a prefix of .safe. For ex:
 | 
				
			||||||
 | 
					  job.startDate is the date as entered by the user. job.safeStartDate is the
 | 
				
			||||||
 | 
					  parsed Moment.js date that we actually use in processing.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _parseDates = function() {
 | 
				
			||||||
 | 
					    var _fmt, replaceDatesInObject, that;
 | 
				
			||||||
 | 
					    _fmt = require('./fluent-date').fmt;
 | 
				
			||||||
 | 
					    that = this;
 | 
				
			||||||
 | 
					    replaceDatesInObject = function(obj) {
 | 
				
			||||||
 | 
					      if (!obj) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (Object.prototype.toString.call(obj) === '[object Array]') {
 | 
				
			||||||
 | 
					        obj.forEach(function(elem) {
 | 
				
			||||||
 | 
					          return replaceDatesInObject(elem);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else if (typeof obj === 'object') {
 | 
				
			||||||
 | 
					        if (obj._isAMomentObject || obj.safe) {
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Object.keys(obj).forEach(function(key) {
 | 
				
			||||||
 | 
					          return replaceDatesInObject(obj[key]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        ['start', 'end', 'date'].forEach(function(val) {
 | 
				
			||||||
 | 
					          if ((obj[val] !== void 0) && (!obj.safe || !obj.safe[val])) {
 | 
				
			||||||
 | 
					            obj.safe = obj.safe || {};
 | 
				
			||||||
 | 
					            obj.safe[val] = _fmt(obj[val]);
 | 
				
			||||||
 | 
					            if (obj[val] && (val === 'start') && !obj.end) {
 | 
				
			||||||
 | 
					              obj.safe.end = _fmt('current');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    Object.keys(this).forEach(function(member) {
 | 
				
			||||||
 | 
					      replaceDatesInObject(that[member]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Export the Sheet function/ctor. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = FreshResume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=fresh-resume.js.map
 | 
				
			||||||
							
								
								
									
										230
									
								
								dist/core/fresh-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								dist/core/fresh-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the FRESHTheme class.
 | 
				
			||||||
 | 
					@module core/fresh-theme
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var EXTEND, FRESHTheme, FS, HMSTATUS, PATH, READFILES, _, _load, _loadOne, friendlyName, loadSafeJson, moment, parsePath, pathExists, validator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validator = require('is-my-json-valid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsePath = require('parse-filepath');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pathExists = require('path-exists').sync;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EXTEND = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('./status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loadSafeJson = require('../utils/safe-json-loader');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  READFILES = require('recursive-readdir-sync');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* A representation of a FRESH theme asset.
 | 
				
			||||||
 | 
					  @class FRESHTheme
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FRESHTheme = (function() {
 | 
				
			||||||
 | 
					    function FRESHTheme() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Open and parse the specified theme. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FRESHTheme.prototype.open = function(themeFolder) {
 | 
				
			||||||
 | 
					      var cached, formatsHash, pathInfo, that, themeFile, themeInfo;
 | 
				
			||||||
 | 
					      this.folder = themeFolder;
 | 
				
			||||||
 | 
					      pathInfo = parsePath(themeFolder);
 | 
				
			||||||
 | 
					      formatsHash = {};
 | 
				
			||||||
 | 
					      themeFile = PATH.join(themeFolder, 'theme.json');
 | 
				
			||||||
 | 
					      themeInfo = loadSafeJson(themeFile);
 | 
				
			||||||
 | 
					      if (themeInfo.ex) {
 | 
				
			||||||
 | 
					        throw {
 | 
				
			||||||
 | 
					          fluenterror: themeInfo.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError,
 | 
				
			||||||
 | 
					          inner: themeInfo.ex.inner
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      that = this;
 | 
				
			||||||
 | 
					      EXTEND(true, this, themeInfo.json);
 | 
				
			||||||
 | 
					      if (this.inherits) {
 | 
				
			||||||
 | 
					        cached = {};
 | 
				
			||||||
 | 
					        _.each(this.inherits, function(th, key) {
 | 
				
			||||||
 | 
					          var d, themePath, themesFolder;
 | 
				
			||||||
 | 
					          themesFolder = require.resolve('fresh-themes');
 | 
				
			||||||
 | 
					          d = parsePath(themeFolder).dirname;
 | 
				
			||||||
 | 
					          themePath = PATH.join(d, th);
 | 
				
			||||||
 | 
					          cached[th] = cached[th] || new FRESHTheme().open(themePath);
 | 
				
			||||||
 | 
					          return formatsHash[key] = cached[th].getFormat(key);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      formatsHash = _load.call(this, formatsHash);
 | 
				
			||||||
 | 
					      this.formats = formatsHash;
 | 
				
			||||||
 | 
					      this.name = parsePath(this.folder).name;
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Determine if the theme supports the specified output format. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FRESHTheme.prototype.hasFormat = function(fmt) {
 | 
				
			||||||
 | 
					      return _.has(this.formats, fmt);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Determine if the theme supports the specified output format. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FRESHTheme.prototype.getFormat = function(fmt) {
 | 
				
			||||||
 | 
					      return this.formats[fmt];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return FRESHTheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Load and parse theme source files. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _load = function(formatsHash) {
 | 
				
			||||||
 | 
					    var copyOnly, fmts, major, that, tplFolder;
 | 
				
			||||||
 | 
					    that = this;
 | 
				
			||||||
 | 
					    major = false;
 | 
				
			||||||
 | 
					    tplFolder = PATH.join(this.folder, 'src');
 | 
				
			||||||
 | 
					    copyOnly = ['.ttf', '.otf', '.png', '.jpg', '.jpeg', '.pdf'];
 | 
				
			||||||
 | 
					    fmts = READFILES(tplFolder).map(function(absPath) {
 | 
				
			||||||
 | 
					      return _loadOne.call(this, absPath, formatsHash, tplFolder);
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    this.cssFiles = fmts.filter(function(fmt) {
 | 
				
			||||||
 | 
					      return fmt && (fmt.ext === 'css');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.cssFiles.forEach(function(cssf) {
 | 
				
			||||||
 | 
					      var idx;
 | 
				
			||||||
 | 
					      idx = _.findIndex(fmts, function(fmt) {
 | 
				
			||||||
 | 
					        return fmt && fmt.pre === cssf.pre && fmt.ext === 'html';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      cssf.major = false;
 | 
				
			||||||
 | 
					      if (idx > -1) {
 | 
				
			||||||
 | 
					        fmts[idx].css = cssf.data;
 | 
				
			||||||
 | 
					        return fmts[idx].cssPath = cssf.path;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (that.inherits) {
 | 
				
			||||||
 | 
					          return that.overrides = {
 | 
				
			||||||
 | 
					            file: cssf.path,
 | 
				
			||||||
 | 
					            data: cssf.data
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return formatsHash;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Load a single theme file. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _loadOne = function(absPath, formatsHash, tplFolder) {
 | 
				
			||||||
 | 
					    var absPathSafe, act, defFormats, idx, isPrimary, obj, outFmt, pathInfo, portion, ref, ref1, reg, res;
 | 
				
			||||||
 | 
					    pathInfo = parsePath(absPath);
 | 
				
			||||||
 | 
					    absPathSafe = absPath.trim().toLowerCase();
 | 
				
			||||||
 | 
					    outFmt = '';
 | 
				
			||||||
 | 
					    act = 'copy';
 | 
				
			||||||
 | 
					    isPrimary = false;
 | 
				
			||||||
 | 
					    if (this.explicit) {
 | 
				
			||||||
 | 
					      outFmt = _.find(Object.keys(this.formats), function(fmtKey) {
 | 
				
			||||||
 | 
					        var fmtVal;
 | 
				
			||||||
 | 
					        fmtVal = this.formats[fmtKey];
 | 
				
			||||||
 | 
					        return _.some(fmtVal.transform, function(fpath) {
 | 
				
			||||||
 | 
					          var absPathB;
 | 
				
			||||||
 | 
					          absPathB = PATH.join(this.folder, fpath).trim().toLowerCase();
 | 
				
			||||||
 | 
					          return absPathB === absPathSafe;
 | 
				
			||||||
 | 
					        }, this);
 | 
				
			||||||
 | 
					      }, this);
 | 
				
			||||||
 | 
					      if (outFmt) {
 | 
				
			||||||
 | 
					        act = 'transform';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!outFmt) {
 | 
				
			||||||
 | 
					      portion = pathInfo.dirname.replace(tplFolder, '');
 | 
				
			||||||
 | 
					      if (portion && portion.trim()) {
 | 
				
			||||||
 | 
					        if (portion[1] === '_') {
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig;
 | 
				
			||||||
 | 
					        res = reg.exec(portion);
 | 
				
			||||||
 | 
					        if (res) {
 | 
				
			||||||
 | 
					          if (res[1] !== 'partials') {
 | 
				
			||||||
 | 
					            outFmt = res[1];
 | 
				
			||||||
 | 
					            if (!this.explicit) {
 | 
				
			||||||
 | 
					              act = 'transform';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            this.partials = this.partials || [];
 | 
				
			||||||
 | 
					            this.partials.push({
 | 
				
			||||||
 | 
					              name: pathInfo.name,
 | 
				
			||||||
 | 
					              path: absPath
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!outFmt) {
 | 
				
			||||||
 | 
					      idx = pathInfo.name.lastIndexOf('-');
 | 
				
			||||||
 | 
					      outFmt = idx === -1 ? pathInfo.name : pathInfo.name.substr(idx + 1);
 | 
				
			||||||
 | 
					      if (!this.explicit) {
 | 
				
			||||||
 | 
					        act = 'transform';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      defFormats = require('./default-formats');
 | 
				
			||||||
 | 
					      isPrimary = _.some(defFormats, function(form) {
 | 
				
			||||||
 | 
					        return form.name === outFmt && pathInfo.extname !== '.css';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    formatsHash[outFmt] = formatsHash[outFmt] || {
 | 
				
			||||||
 | 
					      outFormat: outFmt,
 | 
				
			||||||
 | 
					      files: []
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if ((ref = this.formats) != null ? (ref1 = ref[outFmt]) != null ? ref1.symLinks : void 0 : void 0) {
 | 
				
			||||||
 | 
					      formatsHash[outFmt].symLinks = this.formats[outFmt].symLinks;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    obj = {
 | 
				
			||||||
 | 
					      action: act,
 | 
				
			||||||
 | 
					      primary: isPrimary,
 | 
				
			||||||
 | 
					      path: absPath,
 | 
				
			||||||
 | 
					      orgPath: PATH.relative(tplFolder, absPath),
 | 
				
			||||||
 | 
					      ext: pathInfo.extname.slice(1),
 | 
				
			||||||
 | 
					      title: friendlyName(outFmt),
 | 
				
			||||||
 | 
					      pre: outFmt,
 | 
				
			||||||
 | 
					      data: FS.readFileSync(absPath, 'utf8'),
 | 
				
			||||||
 | 
					      css: null
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    formatsHash[outFmt].files.push(obj);
 | 
				
			||||||
 | 
					    return obj;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Return a more friendly name for certain formats. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  friendlyName = function(val) {
 | 
				
			||||||
 | 
					    var friendly;
 | 
				
			||||||
 | 
					    val = (val && val.trim().toLowerCase()) || '';
 | 
				
			||||||
 | 
					    friendly = {
 | 
				
			||||||
 | 
					      yml: 'yaml',
 | 
				
			||||||
 | 
					      md: 'markdown',
 | 
				
			||||||
 | 
					      txt: 'text'
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return friendly[val] || val;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = FRESHTheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=fresh-theme.js.map
 | 
				
			||||||
							
								
								
									
										422
									
								
								dist/core/jrs-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										422
									
								
								dist/core/jrs-resume.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,422 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the JRSResume class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/jrs-resume
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var AbstractResume, CONVERTER, FS, JRSResume, MD, PATH, _, _parseDates, extend, moment, validator,
 | 
				
			||||||
 | 
					    extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extend = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validator = require('is-my-json-valid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CONVERTER = require('fresh-jrs-converter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AbstractResume = require('./abstract-resume');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
 | 
				
			||||||
 | 
					  is an instantiation of that JSON decorated with utility methods.
 | 
				
			||||||
 | 
					  @class JRSResume
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSResume = (function(superClass) {
 | 
				
			||||||
 | 
					    var clear;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    extend1(JRSResume, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function JRSResume() {
 | 
				
			||||||
 | 
					      return JRSResume.__super__.constructor.apply(this, arguments);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Initialize the the JSResume from string. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.parse = function(stringData, opts) {
 | 
				
			||||||
 | 
					      var ref;
 | 
				
			||||||
 | 
					      this.imp = (ref = this.imp) != null ? ref : {
 | 
				
			||||||
 | 
					        raw: stringData
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return this.parseJSON(JSON.parse(stringData), opts);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Initialize the JRSResume object from JSON.
 | 
				
			||||||
 | 
					    Open and parse the specified JRS resume. Merge the JSON object model onto
 | 
				
			||||||
 | 
					    this Sheet instance with extend() and convert sheet dates to a safe &
 | 
				
			||||||
 | 
					    consistent format. Then sort each section by startDate descending.
 | 
				
			||||||
 | 
					    @param rep {Object} The raw JSON representation.
 | 
				
			||||||
 | 
					    @param opts {Object} Resume loading and parsing options.
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      date: Perform safe date conversion.
 | 
				
			||||||
 | 
					      sort: Sort resume items by date.
 | 
				
			||||||
 | 
					      compute: Prepare computed resume totals.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.parseJSON = function(rep, opts) {
 | 
				
			||||||
 | 
					      var ignoreList, ref, scrubbed, that, traverse;
 | 
				
			||||||
 | 
					      opts = opts || {};
 | 
				
			||||||
 | 
					      that = this;
 | 
				
			||||||
 | 
					      traverse = require('traverse');
 | 
				
			||||||
 | 
					      ignoreList = [];
 | 
				
			||||||
 | 
					      scrubbed = traverse(rep).map(function(x) {
 | 
				
			||||||
 | 
					        if (!this.isLeaf && this.node.ignore) {
 | 
				
			||||||
 | 
					          if (this.node.ignore === true || this.node.ignore === 'true') {
 | 
				
			||||||
 | 
					            ignoreList.push(this.node);
 | 
				
			||||||
 | 
					            return this.remove();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      extend(true, this, scrubbed);
 | 
				
			||||||
 | 
					      if (!((ref = this.imp) != null ? ref.processed : void 0)) {
 | 
				
			||||||
 | 
					        opts = opts || {};
 | 
				
			||||||
 | 
					        if (opts.imp === void 0 || opts.imp) {
 | 
				
			||||||
 | 
					          this.imp = this.imp || {};
 | 
				
			||||||
 | 
					          this.imp.title = (opts.title || this.imp.title) || this.basics.name;
 | 
				
			||||||
 | 
					          if (!this.imp.raw) {
 | 
				
			||||||
 | 
					            this.imp.raw = JSON.stringify(rep);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.imp.processed = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      (opts.date === void 0 || opts.date) && _parseDates.call(this);
 | 
				
			||||||
 | 
					      (opts.sort === void 0 || opts.sort) && this.sort();
 | 
				
			||||||
 | 
					      if (opts.compute === void 0 || opts.compute) {
 | 
				
			||||||
 | 
					        this.basics.computed = {
 | 
				
			||||||
 | 
					          numYears: this.duration(),
 | 
				
			||||||
 | 
					          keywords: this.keywords()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Save the sheet to disk (for environments that have disk access). */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.save = function(filename) {
 | 
				
			||||||
 | 
					      this.imp.file = filename || this.imp.file;
 | 
				
			||||||
 | 
					      FS.writeFileSync(this.imp.file, this.stringify(this), 'utf8');
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Save the sheet to disk in a specific format, either FRESH or JRS. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.saveAs = function(filename, format) {
 | 
				
			||||||
 | 
					      var newRep, stringRep;
 | 
				
			||||||
 | 
					      if (format === 'JRS') {
 | 
				
			||||||
 | 
					        this.imp.file = filename || this.imp.file;
 | 
				
			||||||
 | 
					        FS.writeFileSync(this.imp.file, this.stringify(), 'utf8');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        newRep = CONVERTER.toFRESH(this);
 | 
				
			||||||
 | 
					        stringRep = CONVERTER.toSTRING(newRep);
 | 
				
			||||||
 | 
					        FS.writeFileSync(filename, stringRep, 'utf8');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Return the resume format. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.format = function() {
 | 
				
			||||||
 | 
					      return 'JRS';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.stringify = function() {
 | 
				
			||||||
 | 
					      return JRSResume.stringify(this);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Return a unique list of all keywords across all skills. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.keywords = function() {
 | 
				
			||||||
 | 
					      var flatSkills;
 | 
				
			||||||
 | 
					      flatSkills = [];
 | 
				
			||||||
 | 
					      if (this.skills && this.skills.length) {
 | 
				
			||||||
 | 
					        this.skills.forEach(function(s) {
 | 
				
			||||||
 | 
					          return flatSkills = _.union(flatSkills, s.keywords);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return flatSkills;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return internal metadata. Create if it doesn't exist.
 | 
				
			||||||
 | 
					    JSON Resume v0.0.0 doesn't allow additional properties at the root level,
 | 
				
			||||||
 | 
					    so tuck this into the .basic sub-object.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.i = function() {
 | 
				
			||||||
 | 
					      var ref;
 | 
				
			||||||
 | 
					      return this.imp = (ref = this.imp) != null ? ref : {};
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Reset the sheet to an empty state. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clear = function(clearMeta) {
 | 
				
			||||||
 | 
					      clearMeta = ((clearMeta === void 0) && true) || clearMeta;
 | 
				
			||||||
 | 
					      if (clearMeta) {
 | 
				
			||||||
 | 
					        delete this.imp;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      delete this.basics.computed;
 | 
				
			||||||
 | 
					      delete this.work;
 | 
				
			||||||
 | 
					      delete this.volunteer;
 | 
				
			||||||
 | 
					      delete this.education;
 | 
				
			||||||
 | 
					      delete this.awards;
 | 
				
			||||||
 | 
					      delete this.publications;
 | 
				
			||||||
 | 
					      delete this.interests;
 | 
				
			||||||
 | 
					      delete this.skills;
 | 
				
			||||||
 | 
					      return delete this.basics.profiles;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Add work experience to the sheet. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.add = function(moniker) {
 | 
				
			||||||
 | 
					      var defSheet, newObject;
 | 
				
			||||||
 | 
					      defSheet = JRSResume["default"]();
 | 
				
			||||||
 | 
					      newObject = $.extend(true, {}, defSheet[moniker][0]);
 | 
				
			||||||
 | 
					      this[moniker] = this[moniker] || [];
 | 
				
			||||||
 | 
					      this[moniker].push(newObject);
 | 
				
			||||||
 | 
					      return newObject;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Determine if the sheet includes a specific social profile (eg, GitHub). */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.hasProfile = function(socialNetwork) {
 | 
				
			||||||
 | 
					      socialNetwork = socialNetwork.trim().toLowerCase();
 | 
				
			||||||
 | 
					      return this.basics.profiles && _.some(this.basics.profiles, function(p) {
 | 
				
			||||||
 | 
					        return p.network.trim().toLowerCase() === socialNetwork;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Determine if the sheet includes a specific skill. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.hasSkill = function(skill) {
 | 
				
			||||||
 | 
					      skill = skill.trim().toLowerCase();
 | 
				
			||||||
 | 
					      return this.skills && _.some(this.skills, function(sk) {
 | 
				
			||||||
 | 
					        return sk.keywords && _.some(sk.keywords, function(kw) {
 | 
				
			||||||
 | 
					          return kw.trim().toLowerCase() === skill;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Validate the sheet against the JSON Resume schema. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.isValid = function() {
 | 
				
			||||||
 | 
					      var ret, schema, schemaObj, temp, validate;
 | 
				
			||||||
 | 
					      schema = FS.readFileSync(PATH.join(__dirname, 'resume.json'), 'utf8');
 | 
				
			||||||
 | 
					      schemaObj = JSON.parse(schema);
 | 
				
			||||||
 | 
					      validator = require('is-my-json-valid');
 | 
				
			||||||
 | 
					      validate = validator(schemaObj, {
 | 
				
			||||||
 | 
					        formats: {
 | 
				
			||||||
 | 
					          date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      temp = this.imp;
 | 
				
			||||||
 | 
					      delete this.imp;
 | 
				
			||||||
 | 
					      ret = validate(this);
 | 
				
			||||||
 | 
					      this.imp = temp;
 | 
				
			||||||
 | 
					      if (!ret) {
 | 
				
			||||||
 | 
					        this.imp = this.imp || {};
 | 
				
			||||||
 | 
					        this.imp.validationErrors = validate.errors;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.duration = function(unit) {
 | 
				
			||||||
 | 
					      return JRSResume.__super__.duration.call(this, 'work', 'startDate', 'endDate', unit);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Sort dated things on the sheet by start date descending. Assumes that dates
 | 
				
			||||||
 | 
					    on the sheet have been processed with _parseDates().
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.sort = function() {
 | 
				
			||||||
 | 
					      var byDateDesc;
 | 
				
			||||||
 | 
					      byDateDesc = function(a, b) {
 | 
				
			||||||
 | 
					        if (a.safeStartDate.isBefore(b.safeStartDate)) {
 | 
				
			||||||
 | 
					          return 1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return (a.safeStartDate.isAfter(b.safeStartDate) && -1) || 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      this.work && this.work.sort(byDateDesc);
 | 
				
			||||||
 | 
					      this.education && this.education.sort(byDateDesc);
 | 
				
			||||||
 | 
					      this.volunteer && this.volunteer.sort(byDateDesc);
 | 
				
			||||||
 | 
					      this.awards && this.awards.sort(function(a, b) {
 | 
				
			||||||
 | 
					        if (a.safeDate.isBefore(b.safeDate)) {
 | 
				
			||||||
 | 
					          return 1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return (a.safeDate.isAfter(b.safeDate) && -1) || 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return this.publications && this.publications.sort(function(a, b) {
 | 
				
			||||||
 | 
					        if (a.safeReleaseDate.isBefore(b.safeReleaseDate)) {
 | 
				
			||||||
 | 
					          return 1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return (a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1) || 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.dupe = function() {
 | 
				
			||||||
 | 
					      var rnew;
 | 
				
			||||||
 | 
					      rnew = new JRSResume();
 | 
				
			||||||
 | 
					      rnew.parse(this.stringify(), {});
 | 
				
			||||||
 | 
					      return rnew;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Create a copy of this resume in which all fields have been interpreted as
 | 
				
			||||||
 | 
					    Markdown.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSResume.prototype.harden = function() {
 | 
				
			||||||
 | 
					      var HD, HDIN, hardenStringsInObject, ret, that;
 | 
				
			||||||
 | 
					      that = this;
 | 
				
			||||||
 | 
					      ret = this.dupe();
 | 
				
			||||||
 | 
					      HD = function(txt) {
 | 
				
			||||||
 | 
					        return '@@@@~' + txt + '~@@@@';
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      HDIN = function(txt) {
 | 
				
			||||||
 | 
					        return HD(txt);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      hardenStringsInObject = function(obj, inline) {
 | 
				
			||||||
 | 
					        if (!obj) {
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        inline = inline === void 0 || inline;
 | 
				
			||||||
 | 
					        if (Object.prototype.toString.call(obj) === '[object Array]') {
 | 
				
			||||||
 | 
					          return obj.forEach(function(elem, idx, ar) {
 | 
				
			||||||
 | 
					            if (typeof elem === 'string' || elem instanceof String) {
 | 
				
			||||||
 | 
					              return ar[idx] = inline ? HDIN(elem) : HD(elem);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              return hardenStringsInObject(elem);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        } else if (typeof obj === 'object') {
 | 
				
			||||||
 | 
					          return Object.keys(obj).forEach(function(key) {
 | 
				
			||||||
 | 
					            var sub;
 | 
				
			||||||
 | 
					            sub = obj[key];
 | 
				
			||||||
 | 
					            if (typeof sub === 'string' || sub instanceof String) {
 | 
				
			||||||
 | 
					              if (_.contains(['skills', 'url', 'website', 'startDate', 'endDate', 'releaseDate', 'date', 'phone', 'email', 'address', 'postalCode', 'city', 'country', 'region'], key)) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              if (key === 'summary') {
 | 
				
			||||||
 | 
					                return obj[key] = HD(obj[key]);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                return obj[key] = inline ? HDIN(obj[key]) : HD(obj[key]);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              return hardenStringsInObject(sub);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      Object.keys(ret).forEach(function(member) {
 | 
				
			||||||
 | 
					        return hardenStringsInObject(ret[member]);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return JRSResume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(AbstractResume);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Get the default (empty) sheet. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSResume["default"] = function() {
 | 
				
			||||||
 | 
					    return new JRSResume().parseJSON(require('fresh-resume-starter').jrs);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Convert this object to a JSON string, sanitizing meta-properties along the
 | 
				
			||||||
 | 
					  way. Don't override .toString().
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSResume.stringify = function(obj) {
 | 
				
			||||||
 | 
					    var replacer;
 | 
				
			||||||
 | 
					    replacer = function(key, value) {
 | 
				
			||||||
 | 
					      var temp;
 | 
				
			||||||
 | 
					      temp = _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], function(val) {
 | 
				
			||||||
 | 
					        return key.trim() === val;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (temp) {
 | 
				
			||||||
 | 
					        return void 0;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return JSON.stringify(obj, replacer, 2);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Convert human-friendly dates into formal Moment.js dates for all collections.
 | 
				
			||||||
 | 
					  We don't want to lose the raw textual date as entered by the user, so we store
 | 
				
			||||||
 | 
					  the Moment-ified date as a separate property with a prefix of .safe. For ex:
 | 
				
			||||||
 | 
					  job.startDate is the date as entered by the user. job.safeStartDate is the
 | 
				
			||||||
 | 
					  parsed Moment.js date that we actually use in processing.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _parseDates = function() {
 | 
				
			||||||
 | 
					    var _fmt;
 | 
				
			||||||
 | 
					    _fmt = require('./fluent-date').fmt;
 | 
				
			||||||
 | 
					    this.work && this.work.forEach(function(job) {
 | 
				
			||||||
 | 
					      job.safeStartDate = _fmt(job.startDate);
 | 
				
			||||||
 | 
					      return job.safeEndDate = _fmt(job.endDate);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.education && this.education.forEach(function(edu) {
 | 
				
			||||||
 | 
					      edu.safeStartDate = _fmt(edu.startDate);
 | 
				
			||||||
 | 
					      return edu.safeEndDate = _fmt(edu.endDate);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.volunteer && this.volunteer.forEach(function(vol) {
 | 
				
			||||||
 | 
					      vol.safeStartDate = _fmt(vol.startDate);
 | 
				
			||||||
 | 
					      return vol.safeEndDate = _fmt(vol.endDate);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.awards && this.awards.forEach(function(awd) {
 | 
				
			||||||
 | 
					      return awd.safeDate = _fmt(awd.date);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return this.publications && this.publications.forEach(function(pub) {
 | 
				
			||||||
 | 
					      return pub.safeReleaseDate = _fmt(pub.releaseDate);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Export the JRSResume function/ctor.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = JRSResume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=jrs-resume.js.map
 | 
				
			||||||
							
								
								
									
										107
									
								
								dist/core/jrs-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								dist/core/jrs-theme.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the JRSTheme class.
 | 
				
			||||||
 | 
					@module core/jrs-theme
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.MD for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var JRSTheme, PATH, _, parsePath, pathExists;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsePath = require('parse-filepath');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pathExists = require('path-exists').sync;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  The JRSTheme class is a representation of a JSON Resume theme asset.
 | 
				
			||||||
 | 
					  @class JRSTheme
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSTheme = (function() {
 | 
				
			||||||
 | 
					    function JRSTheme() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Open and parse the specified theme.
 | 
				
			||||||
 | 
					    @method open
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSTheme.prototype.open = function(thFolder) {
 | 
				
			||||||
 | 
					      var pathInfo, pkgJsonPath, thApi, thPkg;
 | 
				
			||||||
 | 
					      this.folder = thFolder;
 | 
				
			||||||
 | 
					      pathInfo = parsePath(thFolder);
 | 
				
			||||||
 | 
					      pkgJsonPath = PATH.join(thFolder, 'package.json');
 | 
				
			||||||
 | 
					      if (pathExists(pkgJsonPath)) {
 | 
				
			||||||
 | 
					        thApi = require(thFolder);
 | 
				
			||||||
 | 
					        thPkg = require(pkgJsonPath);
 | 
				
			||||||
 | 
					        this.name = thPkg.name;
 | 
				
			||||||
 | 
					        this.render = (thApi && thApi.render) || void 0;
 | 
				
			||||||
 | 
					        this.engine = 'jrs';
 | 
				
			||||||
 | 
					        this.formats = {
 | 
				
			||||||
 | 
					          html: {
 | 
				
			||||||
 | 
					            outFormat: 'html',
 | 
				
			||||||
 | 
					            files: [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                action: 'transform',
 | 
				
			||||||
 | 
					                render: this.render,
 | 
				
			||||||
 | 
					                primary: true,
 | 
				
			||||||
 | 
					                ext: 'html',
 | 
				
			||||||
 | 
					                css: null
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          pdf: {
 | 
				
			||||||
 | 
					            outFormat: 'pdf',
 | 
				
			||||||
 | 
					            files: [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                action: 'transform',
 | 
				
			||||||
 | 
					                render: this.render,
 | 
				
			||||||
 | 
					                primary: true,
 | 
				
			||||||
 | 
					                ext: 'pdf',
 | 
				
			||||||
 | 
					                css: null
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw {
 | 
				
			||||||
 | 
					          fluenterror: HACKMYSTATUS.missingPackageJSON
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Determine if the theme supports the output format.
 | 
				
			||||||
 | 
					    @method hasFormat
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSTheme.prototype.hasFormat = function(fmt) {
 | 
				
			||||||
 | 
					      return _.has(this.formats, fmt);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return the requested output format.
 | 
				
			||||||
 | 
					    @method getFormat
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JRSTheme.prototype.getFormat = function(fmt) {
 | 
				
			||||||
 | 
					      return this.formats[fmt];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return JRSTheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = JRSTheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=jrs-theme.js.map
 | 
				
			||||||
							
								
								
									
										122
									
								
								dist/core/resume-factory.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								dist/core/resume-factory.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the ResumeFactory class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/resume-factory
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HACKMYSTATUS, HME, ResumeConverter, ResumeFactory, SyntaxErrorEx, _, _parse, chalk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HACKMYSTATUS = require('./status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HME = require('./event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ResumeConverter = require('fresh-jrs-converter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SyntaxErrorEx = require('../utils/syntax-error-ex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('string.prototype.startswith');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  A simple factory class for FRESH and JSON Resumes.
 | 
				
			||||||
 | 
					  @class ResumeFactory
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ResumeFactory = module.exports = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Load one or more resumes from disk.
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @param {Object} opts An options object with settings for the factory as well
 | 
				
			||||||
 | 
					    as passthrough settings for FRESHResume or JRSResume. Structure:
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          format: 'FRESH',    // Format to open as. ('FRESH', 'JRS', null)
 | 
				
			||||||
 | 
					          objectify: true,    // FRESH/JRSResume or raw JSON?
 | 
				
			||||||
 | 
					          inner: {            // Passthru options for FRESH/JRSResume
 | 
				
			||||||
 | 
					            sort: false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    load: function(sources, opts, emitter) {
 | 
				
			||||||
 | 
					      return sources.map(function(src) {
 | 
				
			||||||
 | 
					        return this.loadOne(src, opts, emitter);
 | 
				
			||||||
 | 
					      }, this);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Load a single resume from disk. */
 | 
				
			||||||
 | 
					    loadOne: function(src, opts, emitter) {
 | 
				
			||||||
 | 
					      var ResumeClass, info, isFRESH, json, objectify, orgFormat, rez, toFormat;
 | 
				
			||||||
 | 
					      toFormat = opts.format;
 | 
				
			||||||
 | 
					      objectify = opts.objectify;
 | 
				
			||||||
 | 
					      toFormat && (toFormat = toFormat.toLowerCase().trim());
 | 
				
			||||||
 | 
					      info = _parse(src, opts, emitter);
 | 
				
			||||||
 | 
					      if (info.fluenterror) {
 | 
				
			||||||
 | 
					        return info;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      json = info.json;
 | 
				
			||||||
 | 
					      isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@');
 | 
				
			||||||
 | 
					      orgFormat = isFRESH ? 'fresh' : 'jrs';
 | 
				
			||||||
 | 
					      if (toFormat && (orgFormat !== toFormat)) {
 | 
				
			||||||
 | 
					        json = ResumeConverter['to' + toFormat.toUpperCase()](json);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      rez = null;
 | 
				
			||||||
 | 
					      if (objectify) {
 | 
				
			||||||
 | 
					        ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume');
 | 
				
			||||||
 | 
					        rez = new ResumeClass().parseJSON(json, opts.inner);
 | 
				
			||||||
 | 
					        rez.i().file = src;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        file: src,
 | 
				
			||||||
 | 
					        json: info.json,
 | 
				
			||||||
 | 
					        rez: rez
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _parse = function(fileName, opts, eve) {
 | 
				
			||||||
 | 
					    var orgFormat, rawData, ret;
 | 
				
			||||||
 | 
					    rawData = null;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      eve && eve.stat(HME.beforeRead, {
 | 
				
			||||||
 | 
					        file: fileName
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      rawData = FS.readFileSync(fileName, 'utf8');
 | 
				
			||||||
 | 
					      eve && eve.stat(HME.afterRead, {
 | 
				
			||||||
 | 
					        file: fileName,
 | 
				
			||||||
 | 
					        data: rawData
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      eve && eve.stat(HME.beforeParse, {
 | 
				
			||||||
 | 
					        data: rawData
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      ret = {
 | 
				
			||||||
 | 
					        json: JSON.parse(rawData)
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      orgFormat = ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@') ? 'fresh' : 'jrs';
 | 
				
			||||||
 | 
					      eve && eve.stat(HME.afterParse, {
 | 
				
			||||||
 | 
					        file: fileName,
 | 
				
			||||||
 | 
					        data: ret.json,
 | 
				
			||||||
 | 
					        fmt: orgFormat
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        fluenterror: rawData ? HACKMYSTATUS.parseError : HACKMYSTATUS.readError,
 | 
				
			||||||
 | 
					        inner: _error,
 | 
				
			||||||
 | 
					        raw: rawData,
 | 
				
			||||||
 | 
					        file: fileName
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=resume-factory.js.map
 | 
				
			||||||
							
								
								
									
										380
									
								
								dist/core/resume.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								dist/core/resume.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,380 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "http://json-schema.org/draft-04/schema#",
 | 
				
			||||||
 | 
					  "title": "Resume Schema",
 | 
				
			||||||
 | 
					  "type": "object",
 | 
				
			||||||
 | 
					  "additionalProperties": false,
 | 
				
			||||||
 | 
					  "properties": {
 | 
				
			||||||
 | 
					    "basics": {
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "additionalProperties": true,
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "name": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "label": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "description": "e.g. Web Developer"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "picture": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "description": "URL (as per RFC 3986) to a picture in JPEG or PNG format"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "email": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "description": "e.g. thomas@gmail.com",
 | 
				
			||||||
 | 
					          "format": "email"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "phone": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "description": "Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "website": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "description": "URL (as per RFC 3986) to your website, e.g. personal homepage",
 | 
				
			||||||
 | 
					          "format": "uri"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "summary": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "description": "Write a short 2-3 sentence biography about yourself"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "location": {
 | 
				
			||||||
 | 
					          "type": "object",
 | 
				
			||||||
 | 
					          "additionalProperties": true,
 | 
				
			||||||
 | 
					          "properties": {
 | 
				
			||||||
 | 
					            "address": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li."
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "postalCode": {
 | 
				
			||||||
 | 
					              "type": "string"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "city": {
 | 
				
			||||||
 | 
					              "type": "string"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "countryCode": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "region": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "The general region where you live. Can be a US state, or a province, for instance."
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "profiles": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "description": "Specify any number of social networks that you participate in",
 | 
				
			||||||
 | 
					          "additionalItems": false,
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "additionalProperties": true,
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					              "network": {
 | 
				
			||||||
 | 
					                "type": "string",
 | 
				
			||||||
 | 
					                "description": "e.g. Facebook or Twitter"
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              "username": {
 | 
				
			||||||
 | 
					                "type": "string",
 | 
				
			||||||
 | 
					                "description": "e.g. neutralthoughts"
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              "url": {
 | 
				
			||||||
 | 
					                "type": "string",
 | 
				
			||||||
 | 
					                "description": "e.g. http://twitter.com/neutralthoughts"
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "work": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "additionalProperties": true,
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					          "company": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Facebook"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "position": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Software Engineer"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "website": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. http://facebook.com",
 | 
				
			||||||
 | 
					            "format": "uri"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "startDate": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
 | 
				
			||||||
 | 
					            "format": "date"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "endDate": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. 2012-06-29",
 | 
				
			||||||
 | 
					            "format": "date"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "summary": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "Give an overview of your responsibilities at the company"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "highlights": {
 | 
				
			||||||
 | 
					            "type": "array",
 | 
				
			||||||
 | 
					            "description": "Specify multiple accomplishments",
 | 
				
			||||||
 | 
					            "additionalItems": false,
 | 
				
			||||||
 | 
					            "items": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "volunteer": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "additionalProperties": true,
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					          "organization": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Facebook"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "position": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Software Engineer"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "website": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. http://facebook.com",
 | 
				
			||||||
 | 
					            "format": "uri"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "startDate": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "resume.json uses the ISO 8601 date standard e.g. 2014-06-29",
 | 
				
			||||||
 | 
					            "format": "date"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "endDate": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. 2012-06-29",
 | 
				
			||||||
 | 
					            "format": "date"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "summary": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "Give an overview of your responsibilities at the company"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "highlights": {
 | 
				
			||||||
 | 
					            "type": "array",
 | 
				
			||||||
 | 
					            "description": "Specify multiple accomplishments",
 | 
				
			||||||
 | 
					            "additionalItems": false,
 | 
				
			||||||
 | 
					            "items": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "education": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "additionalProperties": true,
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					            "institution": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Massachusetts Institute of Technology"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "area": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Arts"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "studyType": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Bachelor"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "startDate": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. 2014-06-29",
 | 
				
			||||||
 | 
					              "format": "date"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "endDate": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. 2012-06-29",
 | 
				
			||||||
 | 
					              "format": "date"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "gpa": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "grade point average, e.g. 3.67/4.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "courses": {
 | 
				
			||||||
 | 
					              "type": "array",
 | 
				
			||||||
 | 
					              "description": "List notable courses/subjects",
 | 
				
			||||||
 | 
					              "additionalItems": false,
 | 
				
			||||||
 | 
					              "items": {
 | 
				
			||||||
 | 
					                "type": "string",
 | 
				
			||||||
 | 
					                "description": "e.g. H1302 - Introduction to American history"
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "awards": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "description": "Specify any awards you have received throughout your professional career",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					          "type": "object",
 | 
				
			||||||
 | 
					          "additionalProperties": true,
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
					            "title": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. One of the 100 greatest minds of the century"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "date": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. 1989-06-12",
 | 
				
			||||||
 | 
					              "format": "date"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "awarder": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Time Magazine"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "summary": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Received for my work with Quantum Physics"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "publications": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "description": "Specify your publications through your career",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					          "type": "object",
 | 
				
			||||||
 | 
					          "additionalProperties": true,
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
					            "name": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. The World Wide Web"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "publisher": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. IEEE, Computer Magazine"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "releaseDate": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. 1990-08-01"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "website": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "summary": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML."
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "skills": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "description": "List out your professional skill-set",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					          "type": "object",
 | 
				
			||||||
 | 
					          "additionalProperties": true,
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
					            "name": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Web Development"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "level": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Master"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "keywords": {
 | 
				
			||||||
 | 
					              "type": "array",
 | 
				
			||||||
 | 
					              "description": "List some keywords pertaining to this skill",
 | 
				
			||||||
 | 
					              "additionalItems": false,
 | 
				
			||||||
 | 
					              "items": {
 | 
				
			||||||
 | 
					                "type": "string",
 | 
				
			||||||
 | 
					                "description": "e.g. HTML"
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "languages": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "description": "List any other languages you speak",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "additionalProperties": true,
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
					          "language": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. English, Spanish"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "fluency": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Fluent, Beginner"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "interests": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "additionalProperties": true,
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					          "name": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Philosophy"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "keywords": {
 | 
				
			||||||
 | 
					            "type": "array",
 | 
				
			||||||
 | 
					            "additionalItems": false,
 | 
				
			||||||
 | 
					            "items": {
 | 
				
			||||||
 | 
					              "type": "string",
 | 
				
			||||||
 | 
					              "description": "e.g. Friedrich Nietzsche"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "references": {
 | 
				
			||||||
 | 
					      "type": "array",
 | 
				
			||||||
 | 
					      "description": "List references you have received",
 | 
				
			||||||
 | 
					      "additionalItems": false,
 | 
				
			||||||
 | 
					      "items": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "additionalProperties": true,
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					          "name": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Timothy Cook"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "reference": {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing."
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								dist/core/status-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								dist/core/status-codes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Status codes for HackMyResume.
 | 
				
			||||||
 | 
					@module core/status-codes
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.MD for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = {
 | 
				
			||||||
 | 
					    success: 0,
 | 
				
			||||||
 | 
					    themeNotFound: 1,
 | 
				
			||||||
 | 
					    copyCss: 2,
 | 
				
			||||||
 | 
					    resumeNotFound: 3,
 | 
				
			||||||
 | 
					    missingCommand: 4,
 | 
				
			||||||
 | 
					    invalidCommand: 5,
 | 
				
			||||||
 | 
					    resumeNotFoundAlt: 6,
 | 
				
			||||||
 | 
					    inputOutputParity: 7,
 | 
				
			||||||
 | 
					    createNameMissing: 8,
 | 
				
			||||||
 | 
					    pdfGeneration: 9,
 | 
				
			||||||
 | 
					    missingPackageJSON: 10,
 | 
				
			||||||
 | 
					    invalid: 11,
 | 
				
			||||||
 | 
					    invalidFormat: 12,
 | 
				
			||||||
 | 
					    notOnPath: 13,
 | 
				
			||||||
 | 
					    readError: 14,
 | 
				
			||||||
 | 
					    parseError: 15,
 | 
				
			||||||
 | 
					    fileSaveError: 16,
 | 
				
			||||||
 | 
					    generateError: 17,
 | 
				
			||||||
 | 
					    invalidHelperUse: 18,
 | 
				
			||||||
 | 
					    mixedMerge: 19,
 | 
				
			||||||
 | 
					    invokeTemplate: 20,
 | 
				
			||||||
 | 
					    compileTemplate: 21,
 | 
				
			||||||
 | 
					    themeLoad: 22,
 | 
				
			||||||
 | 
					    invalidParamCount: 23,
 | 
				
			||||||
 | 
					    missingParam: 24,
 | 
				
			||||||
 | 
					    createError: 25,
 | 
				
			||||||
 | 
					    validateError: 26
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=status-codes.js.map
 | 
				
			||||||
							
								
								
									
										40
									
								
								dist/generators/base-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								dist/generators/base-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the BaseGenerator class.
 | 
				
			||||||
 | 
					@module generators/base-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					The BaseGenerator class is the root of the generator hierarchy. Functionality
 | 
				
			||||||
 | 
					common to ALL generators lives here.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BaseGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = BaseGenerator = (function() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Base-class initialize. */
 | 
				
			||||||
 | 
					    function BaseGenerator(format) {
 | 
				
			||||||
 | 
					      this.format = format;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Status codes. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    BaseGenerator.prototype.codes = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Generator options. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    BaseGenerator.prototype.opts = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return BaseGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=base-generator.js.map
 | 
				
			||||||
							
								
								
									
										53
									
								
								dist/generators/html-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								dist/generators/html-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the HTMLGenerator class.
 | 
				
			||||||
 | 
					@module generators/html-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HTML, HtmlGenerator, PATH, TemplateGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs-extra');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HTML = require('html');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('string.prototype.endswith');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = HtmlGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(HtmlGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function HtmlGenerator() {
 | 
				
			||||||
 | 
					      HtmlGenerator.__super__.constructor.call(this, 'html');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Copy satellite CSS files to the destination and optionally pretty-print
 | 
				
			||||||
 | 
					    the HTML resume prior to saving.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HtmlGenerator.prototype.onBeforeSave = function(info) {
 | 
				
			||||||
 | 
					      if (info.outputFile.endsWith('.css')) {
 | 
				
			||||||
 | 
					        return info.mk;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.opts.prettify) {
 | 
				
			||||||
 | 
					        return HTML.prettyPrint(info.mk, this.opts.prettify);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return info.mk;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return HtmlGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=html-generator.js.map
 | 
				
			||||||
							
								
								
									
										117
									
								
								dist/generators/html-pdf-cli-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								dist/generators/html-pdf-cli-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the HtmlPdfCLIGenerator class.
 | 
				
			||||||
 | 
					@module generators/html-pdf-generator.js
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HMSTATUS, HtmlPdfCLIGenerator, PATH, SLASH, SPAWN, TemplateGenerator, _, engines,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs-extra');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SLASH = require('slash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPAWN = require('../utils/safe-spawn');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  An HTML-driven PDF resume generator for HackMyResume. Talks to Phantom,
 | 
				
			||||||
 | 
					  wkhtmltopdf, and other PDF engines over a CLI (command-line interface).
 | 
				
			||||||
 | 
					  If an engine isn't installed for a particular platform, error out gracefully.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = HtmlPdfCLIGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(HtmlPdfCLIGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function HtmlPdfCLIGenerator() {
 | 
				
			||||||
 | 
					      HtmlPdfCLIGenerator.__super__.constructor.call(this, 'pdf', 'html');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Generate the binary PDF. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HtmlPdfCLIGenerator.prototype.onBeforeSave = function(info) {
 | 
				
			||||||
 | 
					      var safe_eng;
 | 
				
			||||||
 | 
					      if (info.ext !== 'html' && info.ext !== 'pdf') {
 | 
				
			||||||
 | 
					        return info.mk;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      safe_eng = info.opts.pdf || 'wkhtmltopdf';
 | 
				
			||||||
 | 
					      if (safe_eng === 'phantom') {
 | 
				
			||||||
 | 
					        safe_eng = 'phantomjs';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (_.has(engines, safe_eng)) {
 | 
				
			||||||
 | 
					        this.errHandler = info.opts.errHandler;
 | 
				
			||||||
 | 
					        engines[safe_eng].call(this, info.mk, info.outputFile, this.onError);
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Low-level error callback for spawn(). May be called after HMR process
 | 
				
			||||||
 | 
					    termination, so object references may not be valid here. That's okay; if
 | 
				
			||||||
 | 
					    the references are invalid, the error was already logged. We could use
 | 
				
			||||||
 | 
					    spawn-watch here but that causes issues on legacy Node.js.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HtmlPdfCLIGenerator.prototype.onError = function(ex, param) {
 | 
				
			||||||
 | 
					      var ref;
 | 
				
			||||||
 | 
					      if ((ref = param.errHandler) != null) {
 | 
				
			||||||
 | 
					        if (typeof ref.err === "function") {
 | 
				
			||||||
 | 
					          ref.err(HMSTATUS.pdfGeneration, ex);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return HtmlPdfCLIGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  engines = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Generate a PDF from HTML using wkhtmltopdf's CLI interface.
 | 
				
			||||||
 | 
					    Spawns a child process with `wkhtmltopdf <source> <target>`. wkhtmltopdf
 | 
				
			||||||
 | 
					    must be installed and path-accessible.
 | 
				
			||||||
 | 
					    TODO: If HTML generation has run, reuse that output
 | 
				
			||||||
 | 
					    TODO: Local web server to ease wkhtmltopdf rendering
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    wkhtmltopdf: function(markup, fOut, on_error) {
 | 
				
			||||||
 | 
					      var tempFile;
 | 
				
			||||||
 | 
					      tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
 | 
				
			||||||
 | 
					      FS.writeFileSync(tempFile, markup, 'utf8');
 | 
				
			||||||
 | 
					      SPAWN('wkhtmltopdf', [tempFile, fOut], false, on_error, this);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Generate a PDF from HTML using Phantom's CLI interface.
 | 
				
			||||||
 | 
					    Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
 | 
				
			||||||
 | 
					    must be installed and path-accessible.
 | 
				
			||||||
 | 
					    TODO: If HTML generation has run, reuse that output
 | 
				
			||||||
 | 
					    TODO: Local web server to ease Phantom rendering
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    phantomjs: function(markup, fOut, on_error) {
 | 
				
			||||||
 | 
					      var destPath, scriptPath, sourcePath, tempFile;
 | 
				
			||||||
 | 
					      tempFile = fOut.replace(/\.pdf$/i, '.pdf.html');
 | 
				
			||||||
 | 
					      FS.writeFileSync(tempFile, markup, 'utf8');
 | 
				
			||||||
 | 
					      scriptPath = PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js'));
 | 
				
			||||||
 | 
					      scriptPath = SLASH(scriptPath);
 | 
				
			||||||
 | 
					      sourcePath = SLASH(PATH.relative(process.cwd(), tempFile));
 | 
				
			||||||
 | 
					      destPath = SLASH(PATH.relative(process.cwd(), fOut));
 | 
				
			||||||
 | 
					      SPAWN('phantomjs', [scriptPath, sourcePath, destPath], false, on_error, this);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=html-pdf-cli-generator.js.map
 | 
				
			||||||
							
								
								
									
										75
									
								
								dist/generators/html-png-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								dist/generators/html-png-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the HtmlPngGenerator class.
 | 
				
			||||||
 | 
					@module generators/html-png-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.MD for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HTML, HtmlPngGenerator, PATH, SLASH, SPAWN, TemplateGenerator, phantom,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs-extra');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HTML = require('html');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SLASH = require('slash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPAWN = require('../utils/safe-spawn');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  An HTML-based PNG resume generator for HackMyResume.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = HtmlPngGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(HtmlPngGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function HtmlPngGenerator() {
 | 
				
			||||||
 | 
					      HtmlPngGenerator.__super__.constructor.call(this, 'png', 'html');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HtmlPngGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HtmlPngGenerator.prototype.generate = function(rez, f, opts) {
 | 
				
			||||||
 | 
					      var htmlFile, htmlResults;
 | 
				
			||||||
 | 
					      htmlResults = opts.targets.filter(function(t) {
 | 
				
			||||||
 | 
					        return t.fmt.outFormat === 'html';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      htmlFile = htmlResults[0].final.files.filter(function(fl) {
 | 
				
			||||||
 | 
					        return fl.info.ext === 'html';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      phantom(htmlFile[0].data, f);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return HtmlPngGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Generate a PDF from HTML using Phantom's CLI interface.
 | 
				
			||||||
 | 
					  Spawns a child process with `phantomjs <script> <source> <target>`. Phantom
 | 
				
			||||||
 | 
					  must be installed and path-accessible.
 | 
				
			||||||
 | 
					  TODO: If HTML generation has run, reuse that output
 | 
				
			||||||
 | 
					  TODO: Local web server to ease Phantom rendering
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  phantom = function(markup, fOut) {
 | 
				
			||||||
 | 
					    var destPath, info, scriptPath, sourcePath, tempFile;
 | 
				
			||||||
 | 
					    tempFile = fOut.replace(/\.png$/i, '.png.html');
 | 
				
			||||||
 | 
					    FS.writeFileSync(tempFile, markup, 'utf8');
 | 
				
			||||||
 | 
					    scriptPath = SLASH(PATH.relative(process.cwd(), PATH.resolve(__dirname, '../utils/rasterize.js')));
 | 
				
			||||||
 | 
					    sourcePath = SLASH(PATH.relative(process.cwd(), tempFile));
 | 
				
			||||||
 | 
					    destPath = SLASH(PATH.relative(process.cwd(), fOut));
 | 
				
			||||||
 | 
					    info = SPAWN('phantomjs', [scriptPath, sourcePath, destPath]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=html-png-generator.js.map
 | 
				
			||||||
							
								
								
									
										47
									
								
								dist/generators/json-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								dist/generators/json-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the JsonGenerator class.
 | 
				
			||||||
 | 
					@module generators/json-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BaseGenerator, FJCV, FS, JsonGenerator, _,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BaseGenerator = require('./base-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FJCV = require('fresh-jrs-converter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** The JsonGenerator generates a FRESH or JRS resume as an output. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = JsonGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(JsonGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function JsonGenerator() {
 | 
				
			||||||
 | 
					      JsonGenerator.__super__.constructor.call(this, 'json');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JsonGenerator.prototype.invoke = function(rez) {
 | 
				
			||||||
 | 
					      var altRez;
 | 
				
			||||||
 | 
					      altRez = FJCV['to' + (rez.format() === 'FRESH' ? 'JRS' : 'FRESH')](rez);
 | 
				
			||||||
 | 
					      return altRez = FJCV.toSTRING(altRez);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JsonGenerator.prototype.generate = function(rez, f) {
 | 
				
			||||||
 | 
					      FS.writeFileSync(f, this.invoke(rez), 'utf8');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return JsonGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(BaseGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=json-generator.js.map
 | 
				
			||||||
							
								
								
									
										50
									
								
								dist/generators/json-yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								dist/generators/json-yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the JsonYamlGenerator class.
 | 
				
			||||||
 | 
					@module generators/json-yaml-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BaseGenerator, FS, JsonYamlGenerator, YAML,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BaseGenerator = require('./base-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  YAML = require('yamljs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  JsonYamlGenerator takes a JSON resume object and translates it directly to
 | 
				
			||||||
 | 
					  JSON without a template, producing an equivalent YAML-formatted resume. See
 | 
				
			||||||
 | 
					  also YamlGenerator (yaml-generator.js).
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = JsonYamlGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(JsonYamlGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function JsonYamlGenerator() {
 | 
				
			||||||
 | 
					      JsonYamlGenerator.__super__.constructor.call(this, 'yml');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JsonYamlGenerator.prototype.invoke = function(rez, themeMarkup, cssInfo, opts) {
 | 
				
			||||||
 | 
					      return YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JsonYamlGenerator.prototype.generate = function(rez, f, opts) {
 | 
				
			||||||
 | 
					      var data;
 | 
				
			||||||
 | 
					      data = YAML.stringify(JSON.parse(rez.stringify()), Infinity, 2);
 | 
				
			||||||
 | 
					      FS.writeFileSync(f, data, 'utf8');
 | 
				
			||||||
 | 
					      return data;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return JsonYamlGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(BaseGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=json-yaml-generator.js.map
 | 
				
			||||||
							
								
								
									
										33
									
								
								dist/generators/latex-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/latex-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the LaTeXGenerator class.
 | 
				
			||||||
 | 
					@module generators/latex-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var LaTeXGenerator, TemplateGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  LaTeXGenerator generates a LaTeX resume via TemplateGenerator.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = LaTeXGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(LaTeXGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function LaTeXGenerator() {
 | 
				
			||||||
 | 
					      LaTeXGenerator.__super__.constructor.call(this, 'latex', 'tex');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return LaTeXGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=latex-generator.js.map
 | 
				
			||||||
							
								
								
									
										33
									
								
								dist/generators/markdown-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/markdown-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the MarkdownGenerator class.
 | 
				
			||||||
 | 
					@module generators/markdown-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var MarkdownGenerator, TemplateGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  MarkdownGenerator generates a Markdown-formatted resume via TemplateGenerator.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = MarkdownGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(MarkdownGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function MarkdownGenerator() {
 | 
				
			||||||
 | 
					      MarkdownGenerator.__super__.constructor.call(this, 'md', 'txt');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return MarkdownGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=markdown-generator.js.map
 | 
				
			||||||
							
								
								
									
										288
									
								
								dist/generators/template-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								dist/generators/template-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,288 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the TemplateGenerator class. TODO: Refactor
 | 
				
			||||||
 | 
					@module generators/template-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BaseGenerator, EXTEND, FRESHTheme, FS, JRSTheme, MD, MKDIRP, PATH, TemplateGenerator, XML, _, _defaultOpts, _reg, createSymLinks, freeze, parsePath, unfreeze,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs-extra');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  XML = require('xml-escape');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsePath = require('parse-filepath');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MKDIRP = require('mkdirp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BaseGenerator = require('./base-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EXTEND = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FRESHTheme = require('../core/fresh-theme');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSTheme = require('../core/jrs-theme');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  TemplateGenerator performs resume generation via local Handlebar or Underscore
 | 
				
			||||||
 | 
					  style template expansion and is appropriate for text-based formats like HTML,
 | 
				
			||||||
 | 
					  plain text, and XML versions of Microsoft Word, Excel, and OpenOffice.
 | 
				
			||||||
 | 
					  @class TemplateGenerator
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = TemplateGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(TemplateGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Constructor. Set the output format and template format for this
 | 
				
			||||||
 | 
					    generator. Will usually be called by a derived generator such as
 | 
				
			||||||
 | 
					    HTMLGenerator or MarkdownGenerator.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function TemplateGenerator(outputFormat, templateFormat, cssFile) {
 | 
				
			||||||
 | 
					      TemplateGenerator.__super__.constructor.call(this, outputFormat);
 | 
				
			||||||
 | 
					      this.tplFormat = templateFormat || outputFormat;
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Generate a resume using string-based inputs and outputs without touching
 | 
				
			||||||
 | 
					    the filesystem.
 | 
				
			||||||
 | 
					    @method invoke
 | 
				
			||||||
 | 
					    @param rez A FreshResume object.
 | 
				
			||||||
 | 
					    @param opts Generator options.
 | 
				
			||||||
 | 
					    @returns {Array} An array of objects representing the generated output
 | 
				
			||||||
 | 
					    files.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TemplateGenerator.prototype.invoke = function(rez, opts) {
 | 
				
			||||||
 | 
					      var curFmt, results;
 | 
				
			||||||
 | 
					      opts = opts ? (this.opts = EXTEND(true, {}, _defaultOpts, opts)) : this.opts;
 | 
				
			||||||
 | 
					      curFmt = opts.themeObj.getFormat(this.format);
 | 
				
			||||||
 | 
					      curFmt.files = _.sortBy(curFmt.files, function(fi) {
 | 
				
			||||||
 | 
					        return fi.ext !== 'css';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      results = curFmt.files.map(function(tplInfo, idx) {
 | 
				
			||||||
 | 
					        var trx;
 | 
				
			||||||
 | 
					        if (tplInfo.action === 'transform') {
 | 
				
			||||||
 | 
					          trx = this.transform(rez, tplInfo.data, this.format, opts, opts.themeObj, curFmt);
 | 
				
			||||||
 | 
					          if (tplInfo.ext === 'css') {
 | 
				
			||||||
 | 
					            curFmt.files[idx].data = trx;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            tplInfo.ext === 'html';
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof opts.onTransform === "function") {
 | 
				
			||||||
 | 
					          opts.onTransform(tplInfo);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          info: tplInfo,
 | 
				
			||||||
 | 
					          data: trx
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }, this);
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        files: results
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Generate a resume using file-based inputs and outputs. Requires access
 | 
				
			||||||
 | 
					    to the local filesystem.
 | 
				
			||||||
 | 
					    @method generate
 | 
				
			||||||
 | 
					    @param rez A FreshResume object.
 | 
				
			||||||
 | 
					    @param f Full path to the output resume file to generate.
 | 
				
			||||||
 | 
					    @param opts Generator options.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TemplateGenerator.prototype.generate = function(rez, f, opts) {
 | 
				
			||||||
 | 
					      var curFmt, genInfo, outFolder;
 | 
				
			||||||
 | 
					      this.opts = EXTEND(true, {}, _defaultOpts, opts);
 | 
				
			||||||
 | 
					      genInfo = this.invoke(rez, null);
 | 
				
			||||||
 | 
					      outFolder = parsePath(f).dirname;
 | 
				
			||||||
 | 
					      curFmt = opts.themeObj.getFormat(this.format);
 | 
				
			||||||
 | 
					      genInfo.files.forEach(function(file) {
 | 
				
			||||||
 | 
					        var thisFilePath;
 | 
				
			||||||
 | 
					        file.info.orgPath = file.info.orgPath || '';
 | 
				
			||||||
 | 
					        thisFilePath = file.info.primary ? f : PATH.join(outFolder, file.info.orgPath);
 | 
				
			||||||
 | 
					        if (file.info.action !== 'copy' && this.onBeforeSave) {
 | 
				
			||||||
 | 
					          file.data = this.onBeforeSave({
 | 
				
			||||||
 | 
					            theme: opts.themeObj,
 | 
				
			||||||
 | 
					            outputFile: thisFilePath,
 | 
				
			||||||
 | 
					            mk: file.data,
 | 
				
			||||||
 | 
					            opts: this.opts,
 | 
				
			||||||
 | 
					            ext: file.info.ext
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          if (!file.data) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof opts.beforeWrite === "function") {
 | 
				
			||||||
 | 
					          opts.beforeWrite(thisFilePath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        MKDIRP.sync(PATH.dirname(thisFilePath));
 | 
				
			||||||
 | 
					        if (file.info.action !== 'copy') {
 | 
				
			||||||
 | 
					          FS.writeFileSync(thisFilePath, file.data, {
 | 
				
			||||||
 | 
					            encoding: 'utf8',
 | 
				
			||||||
 | 
					            flags: 'w'
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          FS.copySync(file.info.path, thisFilePath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof opts.afterWrite === "function") {
 | 
				
			||||||
 | 
					          opts.afterWrite(thisFilePath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.onAfterSave) {
 | 
				
			||||||
 | 
					          return this.onAfterSave({
 | 
				
			||||||
 | 
					            outputFile: fileName,
 | 
				
			||||||
 | 
					            mk: file.data,
 | 
				
			||||||
 | 
					            opts: this.opts
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }, this);
 | 
				
			||||||
 | 
					      createSymLinks(curFmt, outFolder);
 | 
				
			||||||
 | 
					      return genInfo;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Perform a single resume resume transformation using string-based inputs
 | 
				
			||||||
 | 
					    and outputs without touching the local file system.
 | 
				
			||||||
 | 
					    @param json A FRESH or JRS resume object.
 | 
				
			||||||
 | 
					    @param jst The stringified template data
 | 
				
			||||||
 | 
					    @param format The format name, such as "html" or "latex"
 | 
				
			||||||
 | 
					    @param cssInfo Needs to be refactored.
 | 
				
			||||||
 | 
					    @param opts Options and passthrough data.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TemplateGenerator.prototype.transform = function(json, jst, format, opts, theme, curFmt) {
 | 
				
			||||||
 | 
					      var eng, result;
 | 
				
			||||||
 | 
					      if (this.opts.freezeBreaks) {
 | 
				
			||||||
 | 
					        jst = freeze(jst);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      eng = require('../renderers/' + theme.engine + '-generator');
 | 
				
			||||||
 | 
					      result = eng.generate(json, jst, format, curFmt, opts, theme);
 | 
				
			||||||
 | 
					      if (this.opts.freezeBreaks) {
 | 
				
			||||||
 | 
					        result = unfreeze(result);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return result;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return TemplateGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(BaseGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createSymLinks = function(curFmt, outFolder) {
 | 
				
			||||||
 | 
					    if (curFmt.symLinks) {
 | 
				
			||||||
 | 
					      Object.keys(curFmt.symLinks).forEach(function(loc) {
 | 
				
			||||||
 | 
					        var absLoc, absTarg, succeeded, type;
 | 
				
			||||||
 | 
					        absLoc = PATH.join(outFolder, loc);
 | 
				
			||||||
 | 
					        absTarg = PATH.join(PATH.dirname(absLoc), curFmt.symLinks[loc]);
 | 
				
			||||||
 | 
					        type = parsePath(absLoc).extname ? 'file' : 'junction';
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          return FS.symlinkSync(absTarg, absLoc, type);
 | 
				
			||||||
 | 
					        } catch (_error) {
 | 
				
			||||||
 | 
					          succeeded = false;
 | 
				
			||||||
 | 
					          if (_error.code === 'EEXIST') {
 | 
				
			||||||
 | 
					            FS.unlinkSync(absLoc);
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					              FS.symlinkSync(absTarg, absLoc, type);
 | 
				
			||||||
 | 
					              succeeded = true;
 | 
				
			||||||
 | 
					            } catch (_error) {}
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (!succeeded) {
 | 
				
			||||||
 | 
					            throw ex;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Freeze newlines for protection against errant JST parsers. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  freeze = function(markup) {
 | 
				
			||||||
 | 
					    markup.replace(_reg.regN, _defaultOpts.nSym);
 | 
				
			||||||
 | 
					    return markup.replace(_reg.regR, _defaultOpts.rSym);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Unfreeze newlines when the coast is clear. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unfreeze = function(markup) {
 | 
				
			||||||
 | 
					    markup.replace(_reg.regSymR, '\r');
 | 
				
			||||||
 | 
					    return markup.replace(_reg.regSymN, '\n');
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Default template generator options. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _defaultOpts = {
 | 
				
			||||||
 | 
					    engine: 'underscore',
 | 
				
			||||||
 | 
					    keepBreaks: true,
 | 
				
			||||||
 | 
					    freezeBreaks: false,
 | 
				
			||||||
 | 
					    nSym: '&newl;',
 | 
				
			||||||
 | 
					    rSym: '&retn;',
 | 
				
			||||||
 | 
					    template: {
 | 
				
			||||||
 | 
					      interpolate: /\{\{(.+?)\}\}/g,
 | 
				
			||||||
 | 
					      escape: /\{\{\=(.+?)\}\}/g,
 | 
				
			||||||
 | 
					      evaluate: /\{\%(.+?)\%\}/g,
 | 
				
			||||||
 | 
					      comment: /\{\#(.+?)\#\}/g
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    filters: {
 | 
				
			||||||
 | 
					      out: function(txt) {
 | 
				
			||||||
 | 
					        return txt;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      raw: function(txt) {
 | 
				
			||||||
 | 
					        return txt;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      xml: function(txt) {
 | 
				
			||||||
 | 
					        return XML(txt);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      md: function(txt) {
 | 
				
			||||||
 | 
					        return MD(txt || '');
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      mdin: function(txt) {
 | 
				
			||||||
 | 
					        return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      lower: function(txt) {
 | 
				
			||||||
 | 
					        return txt.toLowerCase();
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      link: function(name, url) {
 | 
				
			||||||
 | 
					        if (url) {
 | 
				
			||||||
 | 
					          return '<a href="' + url + '">' + name + '</a>';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return name;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    prettify: {
 | 
				
			||||||
 | 
					      indent_size: 2,
 | 
				
			||||||
 | 
					      unformatted: ['em', 'strong', 'a'],
 | 
				
			||||||
 | 
					      max_char: 80
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Regexes for linebreak preservation. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _reg = {
 | 
				
			||||||
 | 
					    regN: new RegExp('\n', 'g'),
 | 
				
			||||||
 | 
					    regR: new RegExp('\r', 'g'),
 | 
				
			||||||
 | 
					    regSymN: new RegExp(_defaultOpts.nSym, 'g'),
 | 
				
			||||||
 | 
					    regSymR: new RegExp(_defaultOpts.rSym, 'g')
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=template-generator.js.map
 | 
				
			||||||
							
								
								
									
										33
									
								
								dist/generators/text-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/text-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the TextGenerator class.
 | 
				
			||||||
 | 
					@module generators/text-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var TemplateGenerator, TextGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  The TextGenerator generates a plain-text resume via the TemplateGenerator.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = TextGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(TextGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function TextGenerator() {
 | 
				
			||||||
 | 
					      TextGenerator.__super__.constructor.call(this, 'txt');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return TextGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=text-generator.js.map
 | 
				
			||||||
							
								
								
									
										28
									
								
								dist/generators/word-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								dist/generators/word-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Definition of the WordGenerator class.
 | 
				
			||||||
 | 
					@module generators/word-generator
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var TemplateGenerator, WordGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = WordGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(WordGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function WordGenerator() {
 | 
				
			||||||
 | 
					      WordGenerator.__super__.constructor.call(this, 'doc', 'xml');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return WordGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=word-generator.js.map
 | 
				
			||||||
							
								
								
									
										31
									
								
								dist/generators/xml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								dist/generators/xml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the XMLGenerator class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module generatprs/xml-generator
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BaseGenerator, XMLGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BaseGenerator = require('./base-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** The XmlGenerator generates an XML resume via the TemplateGenerator. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = XMLGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(XMLGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function XMLGenerator() {
 | 
				
			||||||
 | 
					      XMLGenerator.__super__.constructor.call(this, 'xml');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return XMLGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(BaseGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=xml-generator.js.map
 | 
				
			||||||
							
								
								
									
										33
									
								
								dist/generators/yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dist/generators/yaml-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the YAMLGenerator class.
 | 
				
			||||||
 | 
					@module yaml-generator.js
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var TemplateGenerator, YAMLGenerator,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  TemplateGenerator = require('./template-generator');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  YamlGenerator generates a YAML-formatted resume via TemplateGenerator.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = YAMLGenerator = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(YAMLGenerator, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function YAMLGenerator() {
 | 
				
			||||||
 | 
					      YAMLGenerator.__super__.constructor.call(this, 'yml', 'yml');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return YAMLGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(TemplateGenerator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=yaml-generator.js.map
 | 
				
			||||||
							
								
								
									
										71
									
								
								dist/helpers/block-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								dist/helpers/block-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Block helper definitions for HackMyResume / FluentCV.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module helpers/generic-helpers
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BlockHelpers, HMSTATUS, LO, _, unused;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LO = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unused = require('../utils/string');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Block helper function definitions. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BlockHelpers = module.exports = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit the enclosed content if the resume has a section with
 | 
				
			||||||
 | 
					    the specified name. Otherwise, emit an empty string ''.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    section: function(title, options) {
 | 
				
			||||||
 | 
					      var obj, ret;
 | 
				
			||||||
 | 
					      title = title.trim().toLowerCase();
 | 
				
			||||||
 | 
					      obj = LO.get(this.r, title);
 | 
				
			||||||
 | 
					      ret = '';
 | 
				
			||||||
 | 
					      if (obj) {
 | 
				
			||||||
 | 
					        if (_.isArray(obj)) {
 | 
				
			||||||
 | 
					          if (obj.length) {
 | 
				
			||||||
 | 
					            ret = options.fn(this);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (_.isObject(obj)) {
 | 
				
			||||||
 | 
					          if ((obj.history && obj.history.length) || (obj.sets && obj.sets.length)) {
 | 
				
			||||||
 | 
					            ret = options.fn(this);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit the enclosed content if the resume has the named
 | 
				
			||||||
 | 
					    property or subproperty.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    has: function(title, options) {
 | 
				
			||||||
 | 
					      title = title && title.trim().toLowerCase();
 | 
				
			||||||
 | 
					      if (LO.get(this.r, title)) {
 | 
				
			||||||
 | 
					        return options.fn(this);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return true if either value is truthy.
 | 
				
			||||||
 | 
					    @method either
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    either: function(lhs, rhs, options) {
 | 
				
			||||||
 | 
					      if (lhs || rhs) {
 | 
				
			||||||
 | 
					        return options.fn(this);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=block-helpers.js.map
 | 
				
			||||||
							
								
								
									
										66
									
								
								dist/helpers/console-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								dist/helpers/console-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Generic template helper definitions for command-line output.
 | 
				
			||||||
 | 
					@module console-helpers.js
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var CHALK, LO, PAD, _, consoleFormatHelpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PAD = require('string-padding');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LO = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CHALK = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('../utils/string');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  consoleFormatHelpers = module.exports = {
 | 
				
			||||||
 | 
					    v: function(val, defaultVal, padding, style) {
 | 
				
			||||||
 | 
					      var retVal, spaces;
 | 
				
			||||||
 | 
					      retVal = val === null || val === void 0 ? defaultVal : val;
 | 
				
			||||||
 | 
					      spaces = 0;
 | 
				
			||||||
 | 
					      if (String.is(padding)) {
 | 
				
			||||||
 | 
					        spaces = parseInt(padding, 10);
 | 
				
			||||||
 | 
					        if (isNaN(spaces)) {
 | 
				
			||||||
 | 
					          spaces = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else if (_.isNumber(padding)) {
 | 
				
			||||||
 | 
					        spaces = padding;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (spaces !== 0) {
 | 
				
			||||||
 | 
					        retVal = PAD(retVal, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (style && String.is(style)) {
 | 
				
			||||||
 | 
					        retVal = LO.get(CHALK, style)(retVal);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return retVal;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    gapLength: function(val) {
 | 
				
			||||||
 | 
					      if (val < 35) {
 | 
				
			||||||
 | 
					        return CHALK.green.bold(val);
 | 
				
			||||||
 | 
					      } else if (val < 95) {
 | 
				
			||||||
 | 
					        return CHALK.yellow.bold(val);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return CHALK.red.bold(val);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    style: function(val, style) {
 | 
				
			||||||
 | 
					      return LO.get(CHALK, style)(val);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    isPlural: function(val, options) {
 | 
				
			||||||
 | 
					      if (val > 1) {
 | 
				
			||||||
 | 
					        return options.fn(this);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    pad: function(val, spaces) {
 | 
				
			||||||
 | 
					      return PAD(val, Math.abs(spaces), null, spaces > 0 ? PAD.LEFT : PAD.RIGHT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=console-helpers.js.map
 | 
				
			||||||
							
								
								
									
										633
									
								
								dist/helpers/generic-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										633
									
								
								dist/helpers/generic-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,633 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Generic template helper definitions for HackMyResume / FluentCV.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module helpers/generic-helpers
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, FluentDate, GenericHelpers, H2W, HMSTATUS, LO, MD, PATH, XML, _, _fromTo, _reportError, moment, printf, skillLevelToIndex, unused;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  H2W = require('../utils/html-to-wpml');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  XML = require('xml-escape');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = require('../core/fluent-date');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LO = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  printf = require('printf');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unused = require('../utils/string');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Generic template helper function definitions. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  GenericHelpers = module.exports = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit a formatted string representing the specified datetime.
 | 
				
			||||||
 | 
					    Convert the input date to the specified format through Moment.js. If date is
 | 
				
			||||||
 | 
					    valid, return the formatted date string. If date is null, undefined, or other
 | 
				
			||||||
 | 
					    falsy value, return the value of the 'fallback' parameter, if specified, or
 | 
				
			||||||
 | 
					    null if no fallback was specified. If date is invalid, but not null/undefined/
 | 
				
			||||||
 | 
					    falsy, return it as-is.
 | 
				
			||||||
 | 
					    @param {string|Moment} datetime A date value.
 | 
				
			||||||
 | 
					    @param {string} [dtFormat='YYYY-MM'] The desired datetime format. Must be a
 | 
				
			||||||
 | 
					    Moment.js-compatible datetime format.
 | 
				
			||||||
 | 
					    @param {string|Moment} fallback A fallback value to use if the specified date
 | 
				
			||||||
 | 
					    is null, undefined, or falsy.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    formatDate: function(datetime, dtFormat, fallback) {
 | 
				
			||||||
 | 
					      var momentDate;
 | 
				
			||||||
 | 
					      if (datetime == null) {
 | 
				
			||||||
 | 
					        datetime = void 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (dtFormat == null) {
 | 
				
			||||||
 | 
					        dtFormat = 'YYYY-MM';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (datetime && moment.isMoment(datetime)) {
 | 
				
			||||||
 | 
					        return datetime.format(dtFormat);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (String.is(datetime)) {
 | 
				
			||||||
 | 
					        momentDate = moment(datetime, dtFormat);
 | 
				
			||||||
 | 
					        if (momentDate.isValid()) {
 | 
				
			||||||
 | 
					          return momentDate.format(dtFormat);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        momentDate = moment(datetime);
 | 
				
			||||||
 | 
					        if (momentDate.isValid()) {
 | 
				
			||||||
 | 
					          return momentDate.format(dtFormat);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return datetime || (typeof fallback === 'string' ? fallback : (fallback === true ? 'Present' : ''));
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit a formatted string representing the specified datetime.
 | 
				
			||||||
 | 
					    @param {string} dateValue A raw date value from the FRESH or JRS resume.
 | 
				
			||||||
 | 
					    @param {string} [dateFormat='YYYY-MM'] The desired datetime format. Must be
 | 
				
			||||||
 | 
					    compatible with Moment.js datetime formats.
 | 
				
			||||||
 | 
					    @param {string} [dateDefault=null] The default date value to use if the dateValue
 | 
				
			||||||
 | 
					    parameter is null, undefined, or falsy.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    date: function(dateValue, dateFormat, dateDefault) {
 | 
				
			||||||
 | 
					      var dateValueMoment, dateValueSafe, reserved;
 | 
				
			||||||
 | 
					      if (!dateDefault || !String.is(dateDefault)) {
 | 
				
			||||||
 | 
					        dateDefault = 'Current';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!dateFormat || !String.is(dateFormat)) {
 | 
				
			||||||
 | 
					        dateFormat = 'YYYY-MM';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!dateValue || !String.is(dateValue)) {
 | 
				
			||||||
 | 
					        dateValue = null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!dateValue) {
 | 
				
			||||||
 | 
					        return dateDefault;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      reserved = ['current', 'present', 'now'];
 | 
				
			||||||
 | 
					      dateValueSafe = dateValue.trim().toLowerCase();
 | 
				
			||||||
 | 
					      if (_.contains(reserved, dateValueSafe)) {
 | 
				
			||||||
 | 
					        return dateValue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      dateValueMoment = moment(dateValue, dateFormat);
 | 
				
			||||||
 | 
					      if (dateValueMoment.isValid()) {
 | 
				
			||||||
 | 
					        return dateValueMoment.format(dateFormat);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return dateValue;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Given a resume sub-object with a start/end date, format a representation of
 | 
				
			||||||
 | 
					    the date range.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    dateRange: function(obj, fmt, sep, fallback) {
 | 
				
			||||||
 | 
					      if (!obj) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return _fromTo(obj.start, obj.end, fmt, sep, fallback);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Format a from/to date range for display.
 | 
				
			||||||
 | 
					    @method toFrom
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    fromTo: function() {
 | 
				
			||||||
 | 
					      return _fromTo.apply(this, arguments);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return a named color value as an RRGGBB string.
 | 
				
			||||||
 | 
					    @method toFrom
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    color: function(colorName, colorDefault) {
 | 
				
			||||||
 | 
					      var ret;
 | 
				
			||||||
 | 
					      if (!(colorName && colorName.trim())) {
 | 
				
			||||||
 | 
					        return _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					          helper: 'fontList',
 | 
				
			||||||
 | 
					          error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					          expected: 'name'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (!GenericHelpers.theme.colors) {
 | 
				
			||||||
 | 
					          return colorDefault;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ret = GenericHelpers.theme.colors[colorName];
 | 
				
			||||||
 | 
					        if (!(ret && ret.trim())) {
 | 
				
			||||||
 | 
					          return colorDefault;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit the size of the specified named font.
 | 
				
			||||||
 | 
					    @param key {String} A named style from the "fonts" section of the theme's
 | 
				
			||||||
 | 
					    theme.json file. For example: 'default' or 'heading1'.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    fontSize: function(key, defSize, units) {
 | 
				
			||||||
 | 
					      var fontSpec, hasDef, ret;
 | 
				
			||||||
 | 
					      ret = '';
 | 
				
			||||||
 | 
					      hasDef = defSize && (String.is(defSize) || _.isNumber(defSize));
 | 
				
			||||||
 | 
					      if (!(key && key.trim())) {
 | 
				
			||||||
 | 
					        _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					          helper: 'fontSize',
 | 
				
			||||||
 | 
					          error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					          expected: 'key'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					      } else if (GenericHelpers.theme.fonts) {
 | 
				
			||||||
 | 
					        fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
 | 
				
			||||||
 | 
					        if (!fontSpec) {
 | 
				
			||||||
 | 
					          if (GenericHelpers.theme.fonts.all) {
 | 
				
			||||||
 | 
					            fontSpec = GenericHelpers.theme.fonts.all[key];
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (fontSpec) {
 | 
				
			||||||
 | 
					          if (String.is(fontSpec)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          } else if (_.isArray(fontSpec)) {
 | 
				
			||||||
 | 
					            if (!String.is(fontSpec[0])) {
 | 
				
			||||||
 | 
					              ret = fontSpec[0].size;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            ret = fontSpec.size;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!ret) {
 | 
				
			||||||
 | 
					        if (hasDef) {
 | 
				
			||||||
 | 
					          ret = defSize;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					            helper: 'fontSize',
 | 
				
			||||||
 | 
					            error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					            expected: 'defSize'
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          ret = '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit the font face (such as 'Helvetica' or 'Calibri') associated with the
 | 
				
			||||||
 | 
					    provided key.
 | 
				
			||||||
 | 
					    @param key {String} A named style from the "fonts" section of the theme's
 | 
				
			||||||
 | 
					    theme.json file. For example: 'default' or 'heading1'.
 | 
				
			||||||
 | 
					    @param defFont {String} The font to use if the specified key isn't present.
 | 
				
			||||||
 | 
					    Can be any valid font-face name such as 'Helvetica Neue' or 'Calibri'.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    fontFace: function(key, defFont) {
 | 
				
			||||||
 | 
					      var fontSpec, hasDef, ret;
 | 
				
			||||||
 | 
					      ret = '';
 | 
				
			||||||
 | 
					      hasDef = defFont && String.is(defFont);
 | 
				
			||||||
 | 
					      if (!(key && key.trim())) {
 | 
				
			||||||
 | 
					        _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					          helper: 'fontFace',
 | 
				
			||||||
 | 
					          error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					          expected: 'key'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					      } else if (GenericHelpers.theme.fonts) {
 | 
				
			||||||
 | 
					        fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
 | 
				
			||||||
 | 
					        if (!fontSpec) {
 | 
				
			||||||
 | 
					          if (GenericHelpers.theme.fonts.all) {
 | 
				
			||||||
 | 
					            fontSpec = GenericHelpers.theme.fonts.all[key];
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (fontSpec) {
 | 
				
			||||||
 | 
					          if (String.is(fontSpec)) {
 | 
				
			||||||
 | 
					            ret = fontSpec;
 | 
				
			||||||
 | 
					          } else if (_.isArray(fontSpec)) {
 | 
				
			||||||
 | 
					            ret = String.is(fontSpec[0]) ? fontSpec[0] : fontSpec[0].name;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            ret = fontSpec.name;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!(ret && ret.trim())) {
 | 
				
			||||||
 | 
					        ret = defFont;
 | 
				
			||||||
 | 
					        if (!hasDef) {
 | 
				
			||||||
 | 
					          _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					            helper: 'fontFace',
 | 
				
			||||||
 | 
					            error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					            expected: 'defFont'
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          ret = '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit a comma-delimited list of font names suitable associated with the
 | 
				
			||||||
 | 
					    provided key.
 | 
				
			||||||
 | 
					    @param key {String} A named style from the "fonts" section of the theme's
 | 
				
			||||||
 | 
					    theme.json file. For example: 'default' or 'heading1'.
 | 
				
			||||||
 | 
					    @param defFontList {Array} The font list to use if the specified key isn't
 | 
				
			||||||
 | 
					    present. Can be an array of valid font-face name such as 'Helvetica Neue'
 | 
				
			||||||
 | 
					    or 'Calibri'.
 | 
				
			||||||
 | 
					    @param sep {String} The default separator to use in the rendered output.
 | 
				
			||||||
 | 
					    Defaults to ", " (comma with a space).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    fontList: function(key, defFontList, sep) {
 | 
				
			||||||
 | 
					      var fontSpec, hasDef, ret;
 | 
				
			||||||
 | 
					      ret = '';
 | 
				
			||||||
 | 
					      hasDef = defFontList && String.is(defFontList);
 | 
				
			||||||
 | 
					      if (!(key && key.trim())) {
 | 
				
			||||||
 | 
					        _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					          helper: 'fontList',
 | 
				
			||||||
 | 
					          error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					          expected: 'key'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else if (GenericHelpers.theme.fonts) {
 | 
				
			||||||
 | 
					        fontSpec = LO.get(GenericHelpers.theme.fonts, this.format + '.' + key);
 | 
				
			||||||
 | 
					        if (!fontSpec) {
 | 
				
			||||||
 | 
					          if (GenericHelpers.theme.fonts.all) {
 | 
				
			||||||
 | 
					            fontSpec = GenericHelpers.theme.fonts.all[key];
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (fontSpec) {
 | 
				
			||||||
 | 
					          if (String.is(fontSpec)) {
 | 
				
			||||||
 | 
					            ret = fontSpec;
 | 
				
			||||||
 | 
					          } else if (_.isArray(fontSpec)) {
 | 
				
			||||||
 | 
					            fontSpec = fontSpec.map(function(ff) {
 | 
				
			||||||
 | 
					              return "'" + (String.is(ff) ? ff : ff.name) + "'";
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            ret = fontSpec.join(sep === void 0 ? ', ' : sep || '');
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            ret = fontSpec.name;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!(ret && ret.trim())) {
 | 
				
			||||||
 | 
					        if (!hasDef) {
 | 
				
			||||||
 | 
					          _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					            helper: 'fontList',
 | 
				
			||||||
 | 
					            error: HMSTATUS.missingParam,
 | 
				
			||||||
 | 
					            expected: 'defFontList'
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          ret = '';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ret = defFontList;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Capitalize the first letter of the word. TODO: Rename
 | 
				
			||||||
 | 
					    @method section
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    camelCase: function(val) {
 | 
				
			||||||
 | 
					      val = (val && val.trim()) || '';
 | 
				
			||||||
 | 
					      if (val) {
 | 
				
			||||||
 | 
					        return val.charAt(0).toUpperCase() + val.slice(1);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return val;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Display a user-overridable section title for a FRESH resume theme. Use this in
 | 
				
			||||||
 | 
					    lieue of hard-coding section titles.
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Usage:
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        {{sectionTitle "sectionName"}}
 | 
				
			||||||
 | 
					        {{sectionTitle "sectionName" "sectionTitle"}}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Example:
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        {{sectionTitle "Education"}}
 | 
				
			||||||
 | 
					        {{sectionTitle "Employment" "Project History"}}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @param sect_name The name of the section being title. Must be one of the
 | 
				
			||||||
 | 
					    top-level FRESH resume sections ("info", "education", "employment", etc.).
 | 
				
			||||||
 | 
					    @param sect_title The theme-specified section title. May be replaced by the
 | 
				
			||||||
 | 
					    user.
 | 
				
			||||||
 | 
					    @method sectionTitle
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    sectionTitle: function(sname, stitle) {
 | 
				
			||||||
 | 
					      stitle = (stitle && String.is(stitle) && stitle) || sname;
 | 
				
			||||||
 | 
					      return (this.opts.stitles && this.opts.stitles[sname.toLowerCase().trim()]) || stitle;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Convert inline Markdown to inline WordProcessingML. */
 | 
				
			||||||
 | 
					    wpml: function(txt, inline) {
 | 
				
			||||||
 | 
					      if (!txt) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      inline = (inline && !inline.hash) || false;
 | 
				
			||||||
 | 
					      txt = XML(txt.trim());
 | 
				
			||||||
 | 
					      txt = inline ? MD(txt).replace(/^\s*<p>|<\/p>\s*$/gi, '') : MD(txt);
 | 
				
			||||||
 | 
					      txt = H2W(txt);
 | 
				
			||||||
 | 
					      return txt;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Emit a conditional link.
 | 
				
			||||||
 | 
					    @method link
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    link: function(text, url) {
 | 
				
			||||||
 | 
					      if (url && url.trim()) {
 | 
				
			||||||
 | 
					        return '<a href="' + url + '">' + text + '</a>';
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return text;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return the last word of the specified text.
 | 
				
			||||||
 | 
					    @method lastWord
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    lastWord: function(txt) {
 | 
				
			||||||
 | 
					      if (txt && txt.trim()) {
 | 
				
			||||||
 | 
					        return _.last(txt.split(' '));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Convert a skill level to an RGB color triplet. TODO: refactor
 | 
				
			||||||
 | 
					    @method skillColor
 | 
				
			||||||
 | 
					    @param lvl Input skill level. Skill level can be expressed as a string
 | 
				
			||||||
 | 
					    ("beginner", "intermediate", etc.), as an integer (1,5,etc), as a string
 | 
				
			||||||
 | 
					    integer ("1", "5", etc.), or as an RRGGBB color triplet ('#C00000',
 | 
				
			||||||
 | 
					    '#FFFFAA').
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    skillColor: function(lvl) {
 | 
				
			||||||
 | 
					      var idx, skillColors;
 | 
				
			||||||
 | 
					      idx = skillLevelToIndex(lvl);
 | 
				
			||||||
 | 
					      skillColors = (this.theme && this.theme.palette && this.theme.palette.skillLevels) || ['#FFFFFF', '#5CB85C', '#F1C40F', '#428BCA', '#C00000'];
 | 
				
			||||||
 | 
					      return skillColors[idx];
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return an appropriate height. TODO: refactor
 | 
				
			||||||
 | 
					    @method lastWord
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    skillHeight: function(lvl) {
 | 
				
			||||||
 | 
					      var idx;
 | 
				
			||||||
 | 
					      idx = skillLevelToIndex(lvl);
 | 
				
			||||||
 | 
					      return ['38.25', '30', '16', '8', '0'][idx];
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Return all but the last word of the input text.
 | 
				
			||||||
 | 
					    @method initialWords
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    initialWords: function(txt) {
 | 
				
			||||||
 | 
					      if (txt && txt.trim()) {
 | 
				
			||||||
 | 
					        return _.initial(txt.split(' ')).join(' ');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Trim the protocol (http or https) from a URL/
 | 
				
			||||||
 | 
					    @method trimURL
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    trimURL: function(url) {
 | 
				
			||||||
 | 
					      if (url && url.trim()) {
 | 
				
			||||||
 | 
					        return url.trim().replace(/^https?:\/\//i, '');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Convert text to lowercase.
 | 
				
			||||||
 | 
					    @method toLower
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    toLower: function(txt) {
 | 
				
			||||||
 | 
					      if (txt && txt.trim()) {
 | 
				
			||||||
 | 
					        return txt.toLowerCase();
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Convert text to lowercase.
 | 
				
			||||||
 | 
					    @method toLower
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    toUpper: function(txt) {
 | 
				
			||||||
 | 
					      if (txt && txt.trim()) {
 | 
				
			||||||
 | 
					        return txt.toUpperCase();
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Conditional stylesheet link. Creates a link to the specified stylesheet with
 | 
				
			||||||
 | 
					    <link> or embeds the styles inline with <style></style>, depending on the
 | 
				
			||||||
 | 
					    theme author's and user's preferences.
 | 
				
			||||||
 | 
					    @param url {String} The path to the CSS file.
 | 
				
			||||||
 | 
					    @param linkage {String} The default link method. Can be either `embed` or
 | 
				
			||||||
 | 
					    `link`. If omitted, defaults to `embed`. Can be overridden by the `--css`
 | 
				
			||||||
 | 
					    command-line switch.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    styleSheet: function(url, linkage) {
 | 
				
			||||||
 | 
					      var rawCss, renderedCss, ret;
 | 
				
			||||||
 | 
					      linkage = this.opts.css || linkage || 'embed';
 | 
				
			||||||
 | 
					      ret = '';
 | 
				
			||||||
 | 
					      if (linkage === 'link') {
 | 
				
			||||||
 | 
					        ret = printf('<link href="%s" rel="stylesheet" type="text/css">', url);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        rawCss = FS.readFileSync(PATH.join(this.opts.themeObj.folder, '/src/', url), 'utf8');
 | 
				
			||||||
 | 
					        renderedCss = this.engine.generateSimple(this, rawCss);
 | 
				
			||||||
 | 
					        ret = printf('<style>%s</style>', renderedCss);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.opts.themeObj.inherits && this.opts.themeObj.inherits.html && this.format === 'html') {
 | 
				
			||||||
 | 
					        ret += linkage === 'link' ? '<link href="' + this.opts.themeObj.overrides.path + '" rel="stylesheet" type="text/css">' : '<style>' + this.opts.themeObj.overrides.data + '</style>';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Perform a generic comparison.
 | 
				
			||||||
 | 
					    See: http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates
 | 
				
			||||||
 | 
					    @method compare
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    compare: function(lvalue, rvalue, options) {
 | 
				
			||||||
 | 
					      var operator, operators, result;
 | 
				
			||||||
 | 
					      if (arguments.length < 3) {
 | 
				
			||||||
 | 
					        throw new Error("Template helper 'compare' needs 2 parameters");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      operator = options.hash.operator || "==";
 | 
				
			||||||
 | 
					      operators = {
 | 
				
			||||||
 | 
					        '==': function(l, r) {
 | 
				
			||||||
 | 
					          return l === r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '===': function(l, r) {
 | 
				
			||||||
 | 
					          return l === r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '!=': function(l, r) {
 | 
				
			||||||
 | 
					          return l !== r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '<': function(l, r) {
 | 
				
			||||||
 | 
					          return l < r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '>': function(l, r) {
 | 
				
			||||||
 | 
					          return l > r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '<=': function(l, r) {
 | 
				
			||||||
 | 
					          return l <= r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '>=': function(l, r) {
 | 
				
			||||||
 | 
					          return l >= r;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'typeof': function(l, r) {
 | 
				
			||||||
 | 
					          return typeof l === r;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      if (!operators[operator]) {
 | 
				
			||||||
 | 
					        throw new Error("Helper 'compare' doesn't know the operator " + operator);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      result = operators[operator](lvalue, rvalue);
 | 
				
			||||||
 | 
					      if (result) {
 | 
				
			||||||
 | 
					        return options.fn(this);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return options.inverse(this);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    pad: function(stringOrArray, padAmount, unused) {
 | 
				
			||||||
 | 
					      var PAD, ret;
 | 
				
			||||||
 | 
					      stringOrArray = stringOrArray || '';
 | 
				
			||||||
 | 
					      padAmount = padAmount || 0;
 | 
				
			||||||
 | 
					      ret = '';
 | 
				
			||||||
 | 
					      PAD = require('string-padding');
 | 
				
			||||||
 | 
					      if (!String.is(stringOrArray)) {
 | 
				
			||||||
 | 
					        ret = stringOrArray.map(function(line) {
 | 
				
			||||||
 | 
					          return PAD(line, line.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
 | 
				
			||||||
 | 
					        }).join('\n');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        ret = PAD(stringOrArray, stringOrArray.length + Math.abs(padAmount), null, padAmount < 0 ? PAD.LEFT : PAD.RIGHT);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Report an error to the outside world without throwing an exception. Currently
 | 
				
			||||||
 | 
					  relies on kludging the running verb into. opts.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _reportError = function(code, params) {
 | 
				
			||||||
 | 
					    return GenericHelpers.opts.errHandler.err(code, params);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Format a from/to date range for display.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _fromTo = function(dateA, dateB, fmt, sep, fallback) {
 | 
				
			||||||
 | 
					    var dateATrim, dateBTrim, dateFrom, dateTemp, dateTo, reserved;
 | 
				
			||||||
 | 
					    if (moment.isMoment(dateA) || moment.isMoment(dateB)) {
 | 
				
			||||||
 | 
					      _reportError(HMSTATUS.invalidHelperUse, {
 | 
				
			||||||
 | 
					        helper: 'dateRange'
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    dateFrom = null;
 | 
				
			||||||
 | 
					    dateTo = null;
 | 
				
			||||||
 | 
					    dateTemp = null;
 | 
				
			||||||
 | 
					    dateA = dateA || '';
 | 
				
			||||||
 | 
					    dateB = dateB || '';
 | 
				
			||||||
 | 
					    dateATrim = dateA.trim().toLowerCase();
 | 
				
			||||||
 | 
					    dateBTrim = dateB.trim().toLowerCase();
 | 
				
			||||||
 | 
					    reserved = ['current', 'present', 'now', ''];
 | 
				
			||||||
 | 
					    fmt = (fmt && String.is(fmt) && fmt) || 'YYYY-MM';
 | 
				
			||||||
 | 
					    sep = (sep && String.is(sep) && sep) || ' — ';
 | 
				
			||||||
 | 
					    if (_.contains(reserved, dateATrim)) {
 | 
				
			||||||
 | 
					      dateFrom = fallback || '???';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dateTemp = FluentDate.fmt(dateA);
 | 
				
			||||||
 | 
					      dateFrom = dateTemp.format(fmt);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (_.contains(reserved, dateBTrim)) {
 | 
				
			||||||
 | 
					      dateTo = fallback || 'Present';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dateTemp = FluentDate.fmt(dateB);
 | 
				
			||||||
 | 
					      dateTo = dateTemp.format(fmt);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (dateFrom && dateTo) {
 | 
				
			||||||
 | 
					      return dateFrom + sep + dateTo;
 | 
				
			||||||
 | 
					    } else if (dateFrom || dateTo) {
 | 
				
			||||||
 | 
					      return dateFrom || dateTo;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return '';
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  skillLevelToIndex = function(lvl) {
 | 
				
			||||||
 | 
					    var idx, intVal;
 | 
				
			||||||
 | 
					    idx = 0;
 | 
				
			||||||
 | 
					    if (String.is(lvl)) {
 | 
				
			||||||
 | 
					      lvl = lvl.trim().toLowerCase();
 | 
				
			||||||
 | 
					      intVal = parseInt(lvl);
 | 
				
			||||||
 | 
					      if (isNaN(intVal)) {
 | 
				
			||||||
 | 
					        switch (lvl) {
 | 
				
			||||||
 | 
					          case 'beginner':
 | 
				
			||||||
 | 
					            idx = 1;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          case 'intermediate':
 | 
				
			||||||
 | 
					            idx = 2;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          case 'advanced':
 | 
				
			||||||
 | 
					            idx = 3;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          case 'master':
 | 
				
			||||||
 | 
					            idx = 4;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        idx = Math.min(intVal / 2, 4);
 | 
				
			||||||
 | 
					        idx = Math.max(0, idx);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      idx = Math.min(lvl / 2, 4);
 | 
				
			||||||
 | 
					      idx = Math.max(0, idx);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return idx;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=generic-helpers.js.map
 | 
				
			||||||
							
								
								
									
										48
									
								
								dist/helpers/handlebars-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								dist/helpers/handlebars-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Template helper definitions for Handlebars.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module handlebars-helpers.js
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var HANDLEBARS, _, blockHelpers, helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HANDLEBARS = require('handlebars');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  helpers = require('./generic-helpers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  blockHelpers = require('./block-helpers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Register useful Handlebars helpers.
 | 
				
			||||||
 | 
					  @method registerHelpers
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = function(theme, opts) {
 | 
				
			||||||
 | 
					    var wrappedHelpers;
 | 
				
			||||||
 | 
					    helpers.theme = theme;
 | 
				
			||||||
 | 
					    helpers.opts = opts;
 | 
				
			||||||
 | 
					    helpers.type = 'handlebars';
 | 
				
			||||||
 | 
					    wrappedHelpers = _.mapObject(helpers, function(hVal, hKey) {
 | 
				
			||||||
 | 
					      if (_.isFunction(hVal)) {
 | 
				
			||||||
 | 
					        _.wrap(hVal, function(func) {
 | 
				
			||||||
 | 
					          var args;
 | 
				
			||||||
 | 
					          args = Array.prototype.slice.call(arguments);
 | 
				
			||||||
 | 
					          args.shift();
 | 
				
			||||||
 | 
					          args.pop();
 | 
				
			||||||
 | 
					          return func.apply(this, args);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return hVal;
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    HANDLEBARS.registerHelper(wrappedHelpers);
 | 
				
			||||||
 | 
					    HANDLEBARS.registerHelper(blockHelpers);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=handlebars-helpers.js.map
 | 
				
			||||||
							
								
								
									
										38
									
								
								dist/helpers/underscore-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								dist/helpers/underscore-helpers.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Template helper definitions for Underscore.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module handlebars-helpers.js
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var HANDLEBARS, _, helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HANDLEBARS = require('handlebars');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  helpers = require('./generic-helpers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Register useful Underscore helpers.
 | 
				
			||||||
 | 
					  @method registerHelpers
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = function(theme, opts, cssInfo, ctx, eng) {
 | 
				
			||||||
 | 
					    helpers.theme = theme;
 | 
				
			||||||
 | 
					    helpers.opts = opts;
 | 
				
			||||||
 | 
					    helpers.cssInfo = cssInfo;
 | 
				
			||||||
 | 
					    helpers.engine = eng;
 | 
				
			||||||
 | 
					    ctx.h = helpers;
 | 
				
			||||||
 | 
					    _.each(helpers, function(hVal, hKey) {
 | 
				
			||||||
 | 
					      if (_.isFunction(hVal)) {
 | 
				
			||||||
 | 
					        return _.bind(hVal, ctx);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=underscore-helpers.js.map
 | 
				
			||||||
							
								
								
									
										48
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					External API surface for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module hackmycore/index
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** API facade for HackMyResume. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = {
 | 
				
			||||||
 | 
					    verbs: {
 | 
				
			||||||
 | 
					      build: require('./verbs/build'),
 | 
				
			||||||
 | 
					      analyze: require('./verbs/analyze'),
 | 
				
			||||||
 | 
					      validate: require('./verbs/validate'),
 | 
				
			||||||
 | 
					      convert: require('./verbs/convert'),
 | 
				
			||||||
 | 
					      "new": require('./verbs/create'),
 | 
				
			||||||
 | 
					      peek: require('./verbs/peek')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    alias: {
 | 
				
			||||||
 | 
					      generate: require('./verbs/build'),
 | 
				
			||||||
 | 
					      create: require('./verbs/create')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: require('./core/default-options'),
 | 
				
			||||||
 | 
					    formats: require('./core/default-formats'),
 | 
				
			||||||
 | 
					    Sheet: require('./core/fresh-resume'),
 | 
				
			||||||
 | 
					    FRESHResume: require('./core/fresh-resume'),
 | 
				
			||||||
 | 
					    JRSResume: require('./core/jrs-resume'),
 | 
				
			||||||
 | 
					    FRESHTheme: require('./core/fresh-theme'),
 | 
				
			||||||
 | 
					    JRSTheme: require('./core/jrs-theme'),
 | 
				
			||||||
 | 
					    ResumeFactory: require('./core/resume-factory'),
 | 
				
			||||||
 | 
					    FluentDate: require('./core/fluent-date'),
 | 
				
			||||||
 | 
					    HtmlGenerator: require('./generators/html-generator'),
 | 
				
			||||||
 | 
					    TextGenerator: require('./generators/text-generator'),
 | 
				
			||||||
 | 
					    HtmlPdfCliGenerator: require('./generators/html-pdf-cli-generator'),
 | 
				
			||||||
 | 
					    WordGenerator: require('./generators/word-generator'),
 | 
				
			||||||
 | 
					    MarkdownGenerator: require('./generators/markdown-generator'),
 | 
				
			||||||
 | 
					    JsonGenerator: require('./generators/json-generator'),
 | 
				
			||||||
 | 
					    YamlGenerator: require('./generators/yaml-generator'),
 | 
				
			||||||
 | 
					    JsonYamlGenerator: require('./generators/json-yaml-generator'),
 | 
				
			||||||
 | 
					    LaTeXGenerator: require('./generators/latex-generator'),
 | 
				
			||||||
 | 
					    HtmlPngGenerator: require('./generators/html-png-generator')
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=index.js.map
 | 
				
			||||||
							
								
								
									
										140
									
								
								dist/inspectors/gap-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								dist/inspectors/gap-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Employment gap analysis for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module inspectors/gap-inspector
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FluentDate, LO, _, gapInspector, moment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = require('../core/fluent-date');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LO = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Identify gaps in the candidate's employment history.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gapInspector = module.exports = {
 | 
				
			||||||
 | 
					    moniker: 'gap-inspector',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Run the Gap Analyzer on a resume.
 | 
				
			||||||
 | 
					    @method run
 | 
				
			||||||
 | 
					    @return {Array} An array of object representing gaps in the candidate's
 | 
				
			||||||
 | 
					    employment history. Each object provides the start, end, and duration of the
 | 
				
			||||||
 | 
					    gap:
 | 
				
			||||||
 | 
					        { <-- gap
 | 
				
			||||||
 | 
					          start: // A Moment.js date
 | 
				
			||||||
 | 
					          end: // A Moment.js date
 | 
				
			||||||
 | 
					          duration: // Gap length
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    run: function(rez) {
 | 
				
			||||||
 | 
					      var coverage, dur, g, gap_start, hist, new_e, num_gaps, o, ref_count, tdur, total_gap_days;
 | 
				
			||||||
 | 
					      coverage = {
 | 
				
			||||||
 | 
					        gaps: [],
 | 
				
			||||||
 | 
					        overlaps: [],
 | 
				
			||||||
 | 
					        pct: '0%',
 | 
				
			||||||
 | 
					        duration: {
 | 
				
			||||||
 | 
					          total: 0,
 | 
				
			||||||
 | 
					          work: 0,
 | 
				
			||||||
 | 
					          gaps: 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      hist = LO.get(rez, 'employment.history');
 | 
				
			||||||
 | 
					      if (!hist || !hist.length) {
 | 
				
			||||||
 | 
					        return coverage;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      new_e = hist.map(function(job) {
 | 
				
			||||||
 | 
					        var obj;
 | 
				
			||||||
 | 
					        obj = _.pick(job, ['start', 'end']);
 | 
				
			||||||
 | 
					        if (obj && (obj.start || obj.end)) {
 | 
				
			||||||
 | 
					          obj = _.pairs(obj);
 | 
				
			||||||
 | 
					          obj[0][1] = FluentDate.fmt(obj[0][1]);
 | 
				
			||||||
 | 
					          if (obj.length > 1) {
 | 
				
			||||||
 | 
					            obj[1][1] = FluentDate.fmt(obj[1][1]);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return obj;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      new_e = _.filter(_.flatten(new_e, true), function(v) {
 | 
				
			||||||
 | 
					        return v && v.length && v[0] && v[0].length;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (!new_e || !new_e.length) {
 | 
				
			||||||
 | 
					        return coverage;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      new_e = _.sortBy(new_e, function(elem) {
 | 
				
			||||||
 | 
					        return elem[1].unix();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      num_gaps = 0;
 | 
				
			||||||
 | 
					      ref_count = 0;
 | 
				
			||||||
 | 
					      total_gap_days = 0;
 | 
				
			||||||
 | 
					      gap_start = null;
 | 
				
			||||||
 | 
					      new_e.forEach(function(point) {
 | 
				
			||||||
 | 
					        var inc, lastGap, lastOver;
 | 
				
			||||||
 | 
					        inc = point[0] === 'start' ? 1 : -1;
 | 
				
			||||||
 | 
					        ref_count += inc;
 | 
				
			||||||
 | 
					        if (ref_count === 0) {
 | 
				
			||||||
 | 
					          return coverage.gaps.push({
 | 
				
			||||||
 | 
					            start: point[1],
 | 
				
			||||||
 | 
					            end: null
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        } else if (ref_count === 1 && inc === 1) {
 | 
				
			||||||
 | 
					          lastGap = _.last(coverage.gaps);
 | 
				
			||||||
 | 
					          if (lastGap) {
 | 
				
			||||||
 | 
					            lastGap.end = point[1];
 | 
				
			||||||
 | 
					            lastGap.duration = lastGap.end.diff(lastGap.start, 'days');
 | 
				
			||||||
 | 
					            return total_gap_days += lastGap.duration;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (ref_count === 2 && inc === 1) {
 | 
				
			||||||
 | 
					          return coverage.overlaps.push({
 | 
				
			||||||
 | 
					            start: point[1],
 | 
				
			||||||
 | 
					            end: null
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        } else if (ref_count === 1 && inc === -1) {
 | 
				
			||||||
 | 
					          lastOver = _.last(coverage.overlaps);
 | 
				
			||||||
 | 
					          if (lastOver) {
 | 
				
			||||||
 | 
					            lastOver.end = point[1];
 | 
				
			||||||
 | 
					            lastOver.duration = lastOver.end.diff(lastOver.start, 'days');
 | 
				
			||||||
 | 
					            if (lastOver.duration === 0) {
 | 
				
			||||||
 | 
					              return coverage.overlaps.pop();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (coverage.overlaps.length) {
 | 
				
			||||||
 | 
					        o = _.last(coverage.overlaps);
 | 
				
			||||||
 | 
					        if (o && !o.end) {
 | 
				
			||||||
 | 
					          o.end = moment();
 | 
				
			||||||
 | 
					          o.duration = o.end.diff(o.start, 'days');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (coverage.gaps.length) {
 | 
				
			||||||
 | 
					        g = _.last(coverage.gaps);
 | 
				
			||||||
 | 
					        if (g && !g.end) {
 | 
				
			||||||
 | 
					          g.end = moment();
 | 
				
			||||||
 | 
					          g.duration = g.end.diff(g.start, 'days');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      tdur = rez.duration('days');
 | 
				
			||||||
 | 
					      dur = {
 | 
				
			||||||
 | 
					        total: tdur,
 | 
				
			||||||
 | 
					        work: tdur - total_gap_days,
 | 
				
			||||||
 | 
					        gaps: total_gap_days
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      coverage.pct = dur.total > 0 && dur.work > 0 ? (((dur.total - dur.gaps) / dur.total) * 100).toFixed(1) + '%' : '???';
 | 
				
			||||||
 | 
					      coverage.duration = dur;
 | 
				
			||||||
 | 
					      return coverage;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=gap-inspector.js.map
 | 
				
			||||||
							
								
								
									
										63
									
								
								dist/inspectors/keyword-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								dist/inspectors/keyword-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Keyword analysis for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module inspectors/keyword-inspector
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FluentDate, _, keywordInspector;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = require('../core/fluent-date');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Analyze the resume's use of keywords.
 | 
				
			||||||
 | 
					  TODO: BUG: Keyword search regex is inaccurate, especially for one or two
 | 
				
			||||||
 | 
					  letter keywords like "C" or "CLI".
 | 
				
			||||||
 | 
					  @class keywordInspector
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keywordInspector = module.exports = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** A unique name for this inspector. */
 | 
				
			||||||
 | 
					    moniker: 'keyword-inspector',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Run the Keyword Inspector on a resume.
 | 
				
			||||||
 | 
					    @method run
 | 
				
			||||||
 | 
					    @return An collection of statistical keyword data.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    run: function(rez) {
 | 
				
			||||||
 | 
					      var prefix, regex_quote, searchable, suffix;
 | 
				
			||||||
 | 
					      regex_quote = function(str) {
 | 
				
			||||||
 | 
					        return (str + '').replace(/[.?*+^$[\]\\(){}|-]/ig, "\\$&");
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      searchable = '';
 | 
				
			||||||
 | 
					      rez.transformStrings(['imp', 'computed', 'safe'], function(key, val) {
 | 
				
			||||||
 | 
					        return searchable += ' ' + val;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      prefix = '(?:' + ['^', '\\s+', '[\\.,]+'].join('|') + ')';
 | 
				
			||||||
 | 
					      suffix = '(?:' + ['$', '\\s+', '[\\.,]+'].join('|') + ')';
 | 
				
			||||||
 | 
					      return rez.keywords().map(function(kw) {
 | 
				
			||||||
 | 
					        var count, myArray, regex, regex_str;
 | 
				
			||||||
 | 
					        regex_str = prefix + regex_quote(kw) + suffix;
 | 
				
			||||||
 | 
					        regex = new RegExp(regex_str, 'ig');
 | 
				
			||||||
 | 
					        myArray = null;
 | 
				
			||||||
 | 
					        count = 0;
 | 
				
			||||||
 | 
					        while ((myArray = regex.exec(searchable)) !== null) {
 | 
				
			||||||
 | 
					          count++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          name: kw,
 | 
				
			||||||
 | 
					          count: count
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=keyword-inspector.js.map
 | 
				
			||||||
							
								
								
									
										51
									
								
								dist/inspectors/totals-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								dist/inspectors/totals-inspector.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Section analysis for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module inspectors/totals-inspector
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FluentDate, _, totalsInspector;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FluentDate = require('../core/fluent-date');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Retrieve sectional overview and summary information.
 | 
				
			||||||
 | 
					  @class totalsInspector
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  totalsInspector = module.exports = {
 | 
				
			||||||
 | 
					    moniker: 'totals-inspector',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    Run the Totals Inspector on a resume.
 | 
				
			||||||
 | 
					    @method run
 | 
				
			||||||
 | 
					    @return An object containing summary information for each section on the
 | 
				
			||||||
 | 
					    resume.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    run: function(rez) {
 | 
				
			||||||
 | 
					      var sectionTotals;
 | 
				
			||||||
 | 
					      sectionTotals = {};
 | 
				
			||||||
 | 
					      _.each(rez, function(val, key) {
 | 
				
			||||||
 | 
					        if (_.isArray(val) && !_.isString(val)) {
 | 
				
			||||||
 | 
					          return sectionTotals[key] = val.length;
 | 
				
			||||||
 | 
					        } else if (val.history && _.isArray(val.history)) {
 | 
				
			||||||
 | 
					          return sectionTotals[key] = val.history.length;
 | 
				
			||||||
 | 
					        } else if (val.sets && _.isArray(val.sets)) {
 | 
				
			||||||
 | 
					          return sectionTotals[key] = val.sets.length;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        totals: sectionTotals,
 | 
				
			||||||
 | 
					        numSections: Object.keys(sectionTotals).length
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=totals-inspector.js.map
 | 
				
			||||||
							
								
								
									
										102
									
								
								dist/renderers/handlebars-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								dist/renderers/handlebars-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the HandlebarsGenerator class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module renderers/handlebars-generator
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HANDLEBARS, HMSTATUS, HandlebarsGenerator, PATH, READFILES, SLASH, _, parsePath, registerHelpers, registerPartials;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HANDLEBARS = require('handlebars');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  registerHelpers = require('../helpers/handlebars-helpers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsePath = require('parse-filepath');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  READFILES = require('recursive-readdir-sync');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SLASH = require('slash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Perform template-based resume generation using Handlebars.js.
 | 
				
			||||||
 | 
					  @class HandlebarsGenerator
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HandlebarsGenerator = module.exports = {
 | 
				
			||||||
 | 
					    generateSimple: function(data, tpl) {
 | 
				
			||||||
 | 
					      var template;
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        template = HANDLEBARS.compile(tpl, {
 | 
				
			||||||
 | 
					          strict: false,
 | 
				
			||||||
 | 
					          assumeObjects: false
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return template(data);
 | 
				
			||||||
 | 
					      } catch (_error) {
 | 
				
			||||||
 | 
					        throw {
 | 
				
			||||||
 | 
					          fluenterror: HMSTATUS[template ? 'invokeTemplate' : 'compileTemplate'],
 | 
				
			||||||
 | 
					          inner: _error
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    generate: function(json, jst, format, curFmt, opts, theme) {
 | 
				
			||||||
 | 
					      var ctx, encData;
 | 
				
			||||||
 | 
					      registerPartials(format, theme);
 | 
				
			||||||
 | 
					      registerHelpers(theme, opts);
 | 
				
			||||||
 | 
					      encData = json;
 | 
				
			||||||
 | 
					      if (format === 'html' || format === 'pdf') {
 | 
				
			||||||
 | 
					        encData = json.markdownify();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (format === 'doc') {
 | 
				
			||||||
 | 
					        encData = json.xmlify();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ctx = {
 | 
				
			||||||
 | 
					        r: encData,
 | 
				
			||||||
 | 
					        RAW: json,
 | 
				
			||||||
 | 
					        filt: opts.filters,
 | 
				
			||||||
 | 
					        format: format,
 | 
				
			||||||
 | 
					        opts: opts,
 | 
				
			||||||
 | 
					        engine: this,
 | 
				
			||||||
 | 
					        results: curFmt.files,
 | 
				
			||||||
 | 
					        headFragment: opts.headFragment || ''
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return this.generateSimple(ctx, jst);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  registerPartials = function(format, theme) {
 | 
				
			||||||
 | 
					    var partialsFolder;
 | 
				
			||||||
 | 
					    if (_.contains(['html', 'doc', 'md', 'txt', 'pdf'], format)) {
 | 
				
			||||||
 | 
					      partialsFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/partials/', format === 'pdf' ? 'html' : format);
 | 
				
			||||||
 | 
					      _.each(READFILES(partialsFolder, function(error) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }), function(el) {
 | 
				
			||||||
 | 
					        var compiledTemplate, name, pathInfo, tplData;
 | 
				
			||||||
 | 
					        pathInfo = parsePath(el);
 | 
				
			||||||
 | 
					        name = SLASH(PATH.relative(partialsFolder, el).replace(/\.(?:html|xml|hbs|md|txt)$/i, ''));
 | 
				
			||||||
 | 
					        tplData = FS.readFileSync(el, 'utf8');
 | 
				
			||||||
 | 
					        compiledTemplate = HANDLEBARS.compile(tplData);
 | 
				
			||||||
 | 
					        HANDLEBARS.registerPartial(name, compiledTemplate);
 | 
				
			||||||
 | 
					        return theme.partialsInitialized = true;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return _.each(theme.partials, function(el) {
 | 
				
			||||||
 | 
					      var compiledTemplate, tplData;
 | 
				
			||||||
 | 
					      tplData = FS.readFileSync(el.path, 'utf8');
 | 
				
			||||||
 | 
					      compiledTemplate = HANDLEBARS.compile(tplData);
 | 
				
			||||||
 | 
					      return HANDLEBARS.registerPartial(el.name, compiledTemplate);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=handlebars-generator.js.map
 | 
				
			||||||
							
								
								
									
										61
									
								
								dist/renderers/jrs-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								dist/renderers/jrs-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the JRSGenerator class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module renderers/jrs-generator
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HANDLEBARS, JRSGenerator, MD, MDIN, PATH, READFILES, SLASH, _, parsePath, registerHelpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HANDLEBARS = require('handlebars');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  registerHelpers = require('../helpers/handlebars-helpers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsePath = require('parse-filepath');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  READFILES = require('recursive-readdir-sync');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SLASH = require('slash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Perform template-based resume generation for JSON Resume themes.
 | 
				
			||||||
 | 
					  @class JRSGenerator
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSGenerator = module.exports = {
 | 
				
			||||||
 | 
					    generate: function(json, jst, format, cssInfo, opts, theme) {
 | 
				
			||||||
 | 
					      var org, rezHtml, turnoff;
 | 
				
			||||||
 | 
					      turnoff = ['log', 'error', 'dir'];
 | 
				
			||||||
 | 
					      org = turnoff.map(function(c) {
 | 
				
			||||||
 | 
					        var ret;
 | 
				
			||||||
 | 
					        ret = console[c];
 | 
				
			||||||
 | 
					        console[c] = function() {};
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      rezHtml = theme.render(json.harden());
 | 
				
			||||||
 | 
					      turnoff.forEach(function(c, idx) {
 | 
				
			||||||
 | 
					        return console[c] = org[idx];
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return rezHtml = rezHtml.replace(/@@@@~.*?~@@@@/gm, function(val) {
 | 
				
			||||||
 | 
					        return MDIN(val.replace(/~@@@@/gm, '').replace(/@@@@~/gm, ''));
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MDIN = function(txt) {
 | 
				
			||||||
 | 
					    return MD(txt || '').replace(/^\s*<p>|<\/p>\s*$/gi, '');
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=jrs-generator.js.map
 | 
				
			||||||
							
								
								
									
										87
									
								
								dist/renderers/underscore-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								dist/renderers/underscore-generator.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the UnderscoreGenerator class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module underscore-generator.js
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var UnderscoreGenerator, _, escapeLaTeX, registerHelpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  registerHelpers = require('../helpers/underscore-helpers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('../utils/string');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  escapeLaTeX = require('escape-latex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Perform template-based resume generation using Underscore.js.
 | 
				
			||||||
 | 
					  @class UnderscoreGenerator
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  UnderscoreGenerator = module.exports = {
 | 
				
			||||||
 | 
					    generateSimple: function(data, tpl) {
 | 
				
			||||||
 | 
					      var HMS, t;
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        t = _.template(tpl);
 | 
				
			||||||
 | 
					        return t(data);
 | 
				
			||||||
 | 
					      } catch (_error) {
 | 
				
			||||||
 | 
					        HMS = require('../core/status-codes');
 | 
				
			||||||
 | 
					        throw {
 | 
				
			||||||
 | 
					          fluenterror: HMS[t ? 'invokeTemplate' : 'compileTemplate'],
 | 
				
			||||||
 | 
					          inner: _error
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    generate: function(json, jst, format, cssInfo, opts, theme) {
 | 
				
			||||||
 | 
					      var ctx, delims, r, traverse;
 | 
				
			||||||
 | 
					      delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
 | 
				
			||||||
 | 
					      if (opts.themeObj && opts.themeObj.delimeters) {
 | 
				
			||||||
 | 
					        delims = _.mapObject(delims, function(val, key) {
 | 
				
			||||||
 | 
					          return new RegExp(val, "ig");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      _.templateSettings = delims;
 | 
				
			||||||
 | 
					      r = null;
 | 
				
			||||||
 | 
					      switch (format) {
 | 
				
			||||||
 | 
					        case 'html':
 | 
				
			||||||
 | 
					          r = json.markdownify();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'pdf':
 | 
				
			||||||
 | 
					          r = json.markdownify();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'png':
 | 
				
			||||||
 | 
					          r = json.markdownify();
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'latex':
 | 
				
			||||||
 | 
					          traverse = require('traverse');
 | 
				
			||||||
 | 
					          r = traverse(json).map(function(x) {
 | 
				
			||||||
 | 
					            if (this.isLeaf && String.is(this.node)) {
 | 
				
			||||||
 | 
					              return escapeLaTeX(this.node);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return this.node;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          r = json;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ctx = {
 | 
				
			||||||
 | 
					        r: r,
 | 
				
			||||||
 | 
					        filt: opts.filters,
 | 
				
			||||||
 | 
					        XML: require('xml-escape'),
 | 
				
			||||||
 | 
					        RAW: json,
 | 
				
			||||||
 | 
					        cssInfo: cssInfo,
 | 
				
			||||||
 | 
					        headFragment: opts.headFragment || '',
 | 
				
			||||||
 | 
					        opts: opts
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      registerHelpers(theme, opts, cssInfo, ctx, this);
 | 
				
			||||||
 | 
					      return this.generateSimple(ctx, jst);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=underscore-generator.js.map
 | 
				
			||||||
							
								
								
									
										14
									
								
								dist/utils/file-contains.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dist/utils/file-contains.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the SyntaxErrorEx class.
 | 
				
			||||||
 | 
					@module file-contains.js
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = function(file, needle) {
 | 
				
			||||||
 | 
					    return require('fs').readFileSync(file, 'utf-8').indexOf(needle) > -1;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=file-contains.js.map
 | 
				
			||||||
							
								
								
									
										63
									
								
								dist/utils/html-to-wpml.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								dist/utils/html-to-wpml.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the Markdown to WordProcessingML conversion routine.
 | 
				
			||||||
 | 
					@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
 | 
				
			||||||
 | 
					@module utils/html-to-wpml
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var HTML5Tokenizer, _;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HTML5Tokenizer = require('simple-html-tokenizer');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = function(html) {
 | 
				
			||||||
 | 
					    var final, is_bold, is_italic, is_link, link_url, tokens;
 | 
				
			||||||
 | 
					    tokens = HTML5Tokenizer.tokenize(html);
 | 
				
			||||||
 | 
					    final = is_bold = is_italic = is_link = link_url = '';
 | 
				
			||||||
 | 
					    _.each(tokens, function(tok) {
 | 
				
			||||||
 | 
					      var style;
 | 
				
			||||||
 | 
					      switch (tok.type) {
 | 
				
			||||||
 | 
					        case 'StartTag':
 | 
				
			||||||
 | 
					          switch (tok.tagName) {
 | 
				
			||||||
 | 
					            case 'p':
 | 
				
			||||||
 | 
					              return final += '<w:p>';
 | 
				
			||||||
 | 
					            case 'strong':
 | 
				
			||||||
 | 
					              return is_bold = true;
 | 
				
			||||||
 | 
					            case 'em':
 | 
				
			||||||
 | 
					              return is_italic = true;
 | 
				
			||||||
 | 
					            case 'a':
 | 
				
			||||||
 | 
					              is_link = true;
 | 
				
			||||||
 | 
					              return link_url = tok.attributes.filter(function(attr) {
 | 
				
			||||||
 | 
					                return attr[0] === 'href';
 | 
				
			||||||
 | 
					              })[0][1];
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'EndTag':
 | 
				
			||||||
 | 
					          switch (tok.tagName) {
 | 
				
			||||||
 | 
					            case 'p':
 | 
				
			||||||
 | 
					              return final += '</w:p>';
 | 
				
			||||||
 | 
					            case 'strong':
 | 
				
			||||||
 | 
					              return is_bold = false;
 | 
				
			||||||
 | 
					            case 'em':
 | 
				
			||||||
 | 
					              return is_italic = false;
 | 
				
			||||||
 | 
					            case 'a':
 | 
				
			||||||
 | 
					              return is_link = false;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'Chars':
 | 
				
			||||||
 | 
					          if ((tok.chars.trim().length)) {
 | 
				
			||||||
 | 
					            style = is_bold ? '<w:b/>' : '';
 | 
				
			||||||
 | 
					            style += is_italic ? '<w:i/>' : '';
 | 
				
			||||||
 | 
					            style += is_link ? '<w:rStyle w:val="Hyperlink"/>' : '';
 | 
				
			||||||
 | 
					            return final += (is_link ? '<w:hlink w:dest="' + link_url + '">' : '') + '<w:r><w:rPr>' + style + '</w:rPr><w:t>' + tok.chars + '</w:t></w:r>' + (is_link ? '</w:hlink>' : '');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return final;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=html-to-wpml.js.map
 | 
				
			||||||
							
								
								
									
										30
									
								
								dist/utils/md2chalk.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								dist/utils/md2chalk.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Inline Markdown-to-Chalk conversion routines.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module utils/md2chalk
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var CHALK, LO, MD;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CHALK = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LO = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = function(v, style, boldStyle) {
 | 
				
			||||||
 | 
					    var temp;
 | 
				
			||||||
 | 
					    boldStyle = boldStyle || 'bold';
 | 
				
			||||||
 | 
					    temp = v.replace(/\*\*(.*?)\*\*/g, LO.get(CHALK, boldStyle)('$1'));
 | 
				
			||||||
 | 
					    if (style) {
 | 
				
			||||||
 | 
					      return LO.get(CHALK, style)(temp);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return temp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=md2chalk.js.map
 | 
				
			||||||
							
								
								
									
										79
									
								
								dist/utils/rasterize.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								dist/utils/rasterize.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  "use strict";
 | 
				
			||||||
 | 
					  var address, output, page, pageHeight, pageWidth, size, system;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  page = require('webpage').create();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  system = require('system');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  address = output = size = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (system.args.length < 3 || system.args.length > 5) {
 | 
				
			||||||
 | 
					    console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
 | 
				
			||||||
 | 
					    console.log('  paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
 | 
				
			||||||
 | 
					    console.log('  image (png/jpg output) examples: "1920px" entire page, window width 1920px');
 | 
				
			||||||
 | 
					    console.log('                                   "800px*600px" window, clipped to 800x600');
 | 
				
			||||||
 | 
					    phantom.exit(1);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    address = system.args[1];
 | 
				
			||||||
 | 
					    output = system.args[2];
 | 
				
			||||||
 | 
					    page.viewportSize = {
 | 
				
			||||||
 | 
					      width: 600,
 | 
				
			||||||
 | 
					      height: 600
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
 | 
				
			||||||
 | 
					      size = system.args[3].split('*');
 | 
				
			||||||
 | 
					      page.paperSize = size.length === 2 ? {
 | 
				
			||||||
 | 
					        width: size[0],
 | 
				
			||||||
 | 
					        height: size[1],
 | 
				
			||||||
 | 
					        margin: '0px'
 | 
				
			||||||
 | 
					      } : {
 | 
				
			||||||
 | 
					        format: system.args[3],
 | 
				
			||||||
 | 
					        orientation: 'portrait',
 | 
				
			||||||
 | 
					        margin: '1cm'
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    } else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
 | 
				
			||||||
 | 
					      size = system.args[3].split('*');
 | 
				
			||||||
 | 
					      if (size.length === 2) {
 | 
				
			||||||
 | 
					        pageWidth = parseInt(size[0], 10);
 | 
				
			||||||
 | 
					        pageHeight = parseInt(size[1], 10);
 | 
				
			||||||
 | 
					        page.viewportSize = {
 | 
				
			||||||
 | 
					          width: pageWidth,
 | 
				
			||||||
 | 
					          height: pageHeight
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        page.clipRect = {
 | 
				
			||||||
 | 
					          top: 0,
 | 
				
			||||||
 | 
					          left: 0,
 | 
				
			||||||
 | 
					          width: pageWidth,
 | 
				
			||||||
 | 
					          height: pageHeight
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.log("size:", system.args[3]);
 | 
				
			||||||
 | 
					        pageWidth = parseInt(system.args[3], 10);
 | 
				
			||||||
 | 
					        pageHeight = parseInt(pageWidth * 3 / 4, 10);
 | 
				
			||||||
 | 
					        console.log("pageHeight:", pageHeight);
 | 
				
			||||||
 | 
					        page.viewportSize = {
 | 
				
			||||||
 | 
					          width: pageWidth,
 | 
				
			||||||
 | 
					          height: pageHeight
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (system.args.length > 4) {
 | 
				
			||||||
 | 
					      page.zoomFactor = system.args[4];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    page.open(address, function(status) {
 | 
				
			||||||
 | 
					      if (status !== 'success') {
 | 
				
			||||||
 | 
					        console.log('Unable to load the address!');
 | 
				
			||||||
 | 
					        phantom.exit(1);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return window.setTimeout(function() {
 | 
				
			||||||
 | 
					          page.render(output);
 | 
				
			||||||
 | 
					          phantom.exit();
 | 
				
			||||||
 | 
					        }, 200);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=rasterize.js.map
 | 
				
			||||||
							
								
								
									
										34
									
								
								dist/utils/safe-json-loader.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								dist/utils/safe-json-loader.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the SafeJsonLoader class.
 | 
				
			||||||
 | 
					@module utils/safe-json-loader
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, SyntaxErrorEx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SyntaxErrorEx = require('./syntax-error-ex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = function(file) {
 | 
				
			||||||
 | 
					    var ret, retRaw;
 | 
				
			||||||
 | 
					    ret = {};
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      ret.raw = FS.readFileSync(file, 'utf8');
 | 
				
			||||||
 | 
					      ret.json = JSON.parse(ret.raw);
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      retRaw = ret.raw && ret.raw.trim();
 | 
				
			||||||
 | 
					      ret.ex = {
 | 
				
			||||||
 | 
					        operation: retRaw ? 'parse' : 'read',
 | 
				
			||||||
 | 
					        inner: SyntaxErrorEx.is(_error) ? new SyntaxErrorEx(_error, retRaw) : _error,
 | 
				
			||||||
 | 
					        file: file
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=safe-json-loader.js.map
 | 
				
			||||||
							
								
								
									
										46
									
								
								dist/utils/safe-spawn.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								dist/utils/safe-spawn.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Safe spawn utility for HackMyResume / FluentCV.
 | 
				
			||||||
 | 
					@module utils/safe-spawn
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Safely spawn a process synchronously or asynchronously without throwing an
 | 
				
			||||||
 | 
					exception
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  module.exports = function(cmd, args, isSync, callback, param) {
 | 
				
			||||||
 | 
					    var info, spawn;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      spawn = require('child_process')[isSync ? 'spawnSync' : 'spawn'];
 | 
				
			||||||
 | 
					      info = spawn(cmd, args);
 | 
				
			||||||
 | 
					      if (!isSync) {
 | 
				
			||||||
 | 
					        info.on('error', function(err) {
 | 
				
			||||||
 | 
					          if (typeof callback === "function") {
 | 
				
			||||||
 | 
					            callback(err, param);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (info.error) {
 | 
				
			||||||
 | 
					          if (typeof callback === "function") {
 | 
				
			||||||
 | 
					            callback(info.error, param);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            cmd: cmd,
 | 
				
			||||||
 | 
					            inner: info.error
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      if (typeof callback === "function") {
 | 
				
			||||||
 | 
					        callback(_error, param);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return _error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=safe-spawn.js.map
 | 
				
			||||||
							
								
								
									
										64
									
								
								dist/utils/string-transformer.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								dist/utils/string-transformer.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Object string transformation.
 | 
				
			||||||
 | 
					@module utils/string-transformer
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var _, moment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moment = require('moment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Create a copy of this object in which all string fields have been run through
 | 
				
			||||||
 | 
					  a transformation function (such as a Markdown filter or XML encoder).
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = function(ret, filt, transformer) {
 | 
				
			||||||
 | 
					    var that, transformStringsInObject;
 | 
				
			||||||
 | 
					    that = this;
 | 
				
			||||||
 | 
					    transformStringsInObject = function(obj, filters) {
 | 
				
			||||||
 | 
					      if (!obj) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (moment.isMoment(obj)) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (_.isArray(obj)) {
 | 
				
			||||||
 | 
					        return obj.forEach(function(elem, idx, ar) {
 | 
				
			||||||
 | 
					          if (typeof elem === 'string' || elem instanceof String) {
 | 
				
			||||||
 | 
					            return ar[idx] = transformer(null, elem);
 | 
				
			||||||
 | 
					          } else if (_.isObject(elem)) {
 | 
				
			||||||
 | 
					            return transformStringsInObject(elem, filters);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else if (_.isObject(obj)) {
 | 
				
			||||||
 | 
					        return Object.keys(obj).forEach(function(k) {
 | 
				
			||||||
 | 
					          var sub;
 | 
				
			||||||
 | 
					          if (filters.length && _.contains(filters, k)) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          sub = obj[k];
 | 
				
			||||||
 | 
					          if (typeof sub === 'string' || sub instanceof String) {
 | 
				
			||||||
 | 
					            return obj[k] = transformer(k, sub);
 | 
				
			||||||
 | 
					          } else if (_.isObject(sub)) {
 | 
				
			||||||
 | 
					            return transformStringsInObject(sub, filters);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    Object.keys(ret).forEach(function(member) {
 | 
				
			||||||
 | 
					      if (!filt || !filt.length || !_.contains(filt, member)) {
 | 
				
			||||||
 | 
					        return transformStringsInObject(ret[member], filt || []);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=string-transformer.js.map
 | 
				
			||||||
							
								
								
									
										29
									
								
								dist/utils/string.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								dist/utils/string.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definitions of string utility functions.
 | 
				
			||||||
 | 
					@module utils/string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Determine if the string is null, empty, or whitespace.
 | 
				
			||||||
 | 
					See: http://stackoverflow.com/a/32800728/4942583
 | 
				
			||||||
 | 
					@method isNullOrWhitespace
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  String.isNullOrWhitespace = function(input) {
 | 
				
			||||||
 | 
					    return !input || !input.trim();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String.prototype.endsWith = function(suffix) {
 | 
				
			||||||
 | 
					    return this.indexOf(suffix, this.length - suffix.length) !== -1;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String.is = function(val) {
 | 
				
			||||||
 | 
					    return typeof val === 'string' || val instanceof String;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=string.js.map
 | 
				
			||||||
							
								
								
									
										55
									
								
								dist/utils/syntax-error-ex.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								dist/utils/syntax-error-ex.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the SyntaxErrorEx class.
 | 
				
			||||||
 | 
					@module utils/syntax-error-ex
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Represents a SyntaxError exception with line and column info.
 | 
				
			||||||
 | 
					Collect syntax error information from the provided exception object. The
 | 
				
			||||||
 | 
					JavaScript `SyntaxError` exception isn't interpreted uniformly across environ-
 | 
				
			||||||
 | 
					ments, so we reparse on error to grab the line and column.
 | 
				
			||||||
 | 
					See: http://stackoverflow.com/q/13323356
 | 
				
			||||||
 | 
					@class SyntaxErrorEx
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var SyntaxErrorEx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SyntaxErrorEx = (function() {
 | 
				
			||||||
 | 
					    function SyntaxErrorEx(ex, rawData) {
 | 
				
			||||||
 | 
					      var JSONLint, colNum, lineNum, lint, ref;
 | 
				
			||||||
 | 
					      lineNum = null;
 | 
				
			||||||
 | 
					      colNum = null;
 | 
				
			||||||
 | 
					      JSONLint = require('json-lint');
 | 
				
			||||||
 | 
					      lint = JSONLint(rawData, {
 | 
				
			||||||
 | 
					        comments: false
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (lint.error) {
 | 
				
			||||||
 | 
					        ref = [lint.line, lint.character], this.line = ref[0], this.col = ref[1];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!lint.error) {
 | 
				
			||||||
 | 
					        JSONLint = require('jsonlint');
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          JSONLint.parse(rawData);
 | 
				
			||||||
 | 
					        } catch (_error) {
 | 
				
			||||||
 | 
					          this.line = (/on line (\d+)/.exec(_error))[1];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return SyntaxErrorEx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SyntaxErrorEx.is = function(ex) {
 | 
				
			||||||
 | 
					    return ex instanceof SyntaxError;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = SyntaxErrorEx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=syntax-error-ex.js.map
 | 
				
			||||||
							
								
								
									
										110
									
								
								dist/verbs/analyze.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								dist/verbs/analyze.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Implementation of the 'analyze' verb for HackMyResume.
 | 
				
			||||||
 | 
					@module verbs/analyze
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var AnalyzeVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, ResumeFactory, Verb, _, _analyze, _analyzeOne, _loadInspectors, chalk,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MKDIRP = require('mkdirp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ResumeFactory = require('../core/resume-factory');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Verb = require('../verbs/verb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** An invokable resume analysis command. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = AnalyzeVerb = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(AnalyzeVerb, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function AnalyzeVerb() {
 | 
				
			||||||
 | 
					      AnalyzeVerb.__super__.constructor.call(this, 'analyze', _analyze);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AnalyzeVerb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(Verb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Private workhorse for the 'analyze' command. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _analyze = function(sources, dst, opts) {
 | 
				
			||||||
 | 
					    var nlzrs, results;
 | 
				
			||||||
 | 
					    if (!sources || !sources.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.resumeNotFound, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    nlzrs = _loadInspectors();
 | 
				
			||||||
 | 
					    results = _.map(sources, function(src) {
 | 
				
			||||||
 | 
					      var r;
 | 
				
			||||||
 | 
					      r = ResumeFactory.loadOne(src, {
 | 
				
			||||||
 | 
					        format: 'FRESH',
 | 
				
			||||||
 | 
					        objectify: true
 | 
				
			||||||
 | 
					      }, this);
 | 
				
			||||||
 | 
					      if (opts.assert && this.hasError()) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (r.fluenterror) {
 | 
				
			||||||
 | 
					        r.quit = opts.assert;
 | 
				
			||||||
 | 
					        this.err(r.fluenterror, r);
 | 
				
			||||||
 | 
					        return r;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return _analyzeOne.call(this, r, nlzrs, opts);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    if (this.hasError() && !opts.assert) {
 | 
				
			||||||
 | 
					      this.reject(this.errorCode);
 | 
				
			||||||
 | 
					    } else if (!this.hasError()) {
 | 
				
			||||||
 | 
					      this.resolve(results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Analyze a single resume. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _analyzeOne = function(resumeObject, nlzrs, opts) {
 | 
				
			||||||
 | 
					    var info, rez, safeFormat;
 | 
				
			||||||
 | 
					    rez = resumeObject.rez;
 | 
				
			||||||
 | 
					    safeFormat = rez.meta && rez.meta.format && rez.meta.format.startsWith('FRESH') ? 'FRESH' : 'JRS';
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.beforeAnalyze, {
 | 
				
			||||||
 | 
					      fmt: safeFormat,
 | 
				
			||||||
 | 
					      file: resumeObject.file
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    info = _.mapObject(nlzrs, function(val, key) {
 | 
				
			||||||
 | 
					      return val.run(rez);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.afterAnalyze, {
 | 
				
			||||||
 | 
					      info: info
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return info;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _loadInspectors = function() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      totals: require('../inspectors/totals-inspector'),
 | 
				
			||||||
 | 
					      coverage: require('../inspectors/gap-inspector'),
 | 
				
			||||||
 | 
					      keywords: require('../inspectors/keyword-inspector')
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=analyze.js.map
 | 
				
			||||||
							
								
								
									
										450
									
								
								dist/verbs/build.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								dist/verbs/build.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,450 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Implementation of the 'build' verb for HackMyResume.
 | 
				
			||||||
 | 
					@module verbs/build
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var BuildVerb, FRESHTheme, FS, HMEVENT, HMSTATUS, JRSTheme, MD, MKDIRP, PATH, RConverter, RTYPES, ResumeFactory, Verb, _, _addFreebieFormats, _build, _err, _expand, _fmts, _loadTheme, _log, _opts, _prep, _rezObj, _single, _verifyOutputs, _verifyTheme, addFreebieFormats, build, expand, extend, loadTheme, parsePath, prep, single, verifyOutputs, verifyTheme,
 | 
				
			||||||
 | 
					    extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MD = require('marked');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MKDIRP = require('mkdirp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extend = require('extend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsePath = require('parse-filepath');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RConverter = require('fresh-jrs-converter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RTYPES = {
 | 
				
			||||||
 | 
					    FRESH: require('../core/fresh-resume'),
 | 
				
			||||||
 | 
					    JRS: require('../core/jrs-resume')
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _opts = require('../core/default-options');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FRESHTheme = require('../core/fresh-theme');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  JRSTheme = require('../core/jrs-theme');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ResumeFactory = require('../core/resume-factory');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _fmts = require('../core/default-formats');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Verb = require('../verbs/verb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _err = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _log = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _rezObj = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  build = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  prep = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  single = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  verifyOutputs = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  addFreebieFormats = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  expand = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  verifyTheme = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loadTheme = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** An invokable resume generation command. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = BuildVerb = (function(superClass) {
 | 
				
			||||||
 | 
					    extend1(BuildVerb, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Create a new build verb. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function BuildVerb() {
 | 
				
			||||||
 | 
					      BuildVerb.__super__.constructor.call(this, 'build', _build);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return BuildVerb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(Verb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Given a source resume in FRESH or JRS format, a destination resume path, and a
 | 
				
			||||||
 | 
					  theme file, generate 0..N resumes in the desired formats.
 | 
				
			||||||
 | 
					  @param src Path to the source JSON resume file: "rez/resume.json".
 | 
				
			||||||
 | 
					  @param dst An array of paths to the target resume file(s).
 | 
				
			||||||
 | 
					  @param opts Generation options.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _build = function(src, dst, opts) {
 | 
				
			||||||
 | 
					    var inv, isFRESH, mixed, newEx, orgFormat, problemSheets, results, rez, sheetObjects, sheets, tFolder, targets, theme, toFormat;
 | 
				
			||||||
 | 
					    if (!src || !src.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.resumeNotFound, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _prep.call(this, src, dst, opts);
 | 
				
			||||||
 | 
					    sheetObjects = ResumeFactory.load(src, {
 | 
				
			||||||
 | 
					      format: null,
 | 
				
			||||||
 | 
					      objectify: false,
 | 
				
			||||||
 | 
					      quit: true,
 | 
				
			||||||
 | 
					      inner: {
 | 
				
			||||||
 | 
					        sort: _opts.sort
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    problemSheets = _.filter(sheetObjects, function(so) {
 | 
				
			||||||
 | 
					      return so.fluenterror;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (problemSheets && problemSheets.length) {
 | 
				
			||||||
 | 
					      problemSheets[0].quit = true;
 | 
				
			||||||
 | 
					      this.err(problemSheets[0].fluenterror, problemSheets[0]);
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    sheets = sheetObjects.map(function(r) {
 | 
				
			||||||
 | 
					      return r.json;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    theme = null;
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.beforeTheme, {
 | 
				
			||||||
 | 
					      theme: _opts.theme
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      tFolder = _verifyTheme.call(this, _opts.theme);
 | 
				
			||||||
 | 
					      if (tFolder.fluenterror) {
 | 
				
			||||||
 | 
					        tFolder.quit = true;
 | 
				
			||||||
 | 
					        this.err(tFolder.fluenterror, tFolder);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      theme = _opts.themeObj = _loadTheme(tFolder);
 | 
				
			||||||
 | 
					      _addFreebieFormats(theme);
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      newEx = {
 | 
				
			||||||
 | 
					        fluenterror: HMSTATUS.themeLoad,
 | 
				
			||||||
 | 
					        inner: _error,
 | 
				
			||||||
 | 
					        attempted: _opts.theme,
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.themeLoad, newEx);
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.afterTheme, {
 | 
				
			||||||
 | 
					      theme: theme
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    inv = _verifyOutputs.call(this, dst, theme);
 | 
				
			||||||
 | 
					    if (inv && inv.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.invalidFormat, {
 | 
				
			||||||
 | 
					        data: inv,
 | 
				
			||||||
 | 
					        theme: theme,
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    rez = null;
 | 
				
			||||||
 | 
					    if (sheets.length > 1) {
 | 
				
			||||||
 | 
					      isFRESH = !sheets[0].basics;
 | 
				
			||||||
 | 
					      mixed = _.any(sheets, function(s) {
 | 
				
			||||||
 | 
					        if (isFRESH) {
 | 
				
			||||||
 | 
					          return s.basics;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return !s.basics;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.beforeMerge, {
 | 
				
			||||||
 | 
					        f: _.clone(sheetObjects),
 | 
				
			||||||
 | 
					        mixed: mixed
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (mixed) {
 | 
				
			||||||
 | 
					        this.err(HMSTATUS.mixedMerge);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      rez = _.reduceRight(sheets, function(a, b, idx) {
 | 
				
			||||||
 | 
					        return extend(true, b, a);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.afterMerge, {
 | 
				
			||||||
 | 
					        r: rez
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      rez = sheets[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    orgFormat = rez.basics ? 'JRS' : 'FRESH';
 | 
				
			||||||
 | 
					    toFormat = theme.render ? 'JRS' : 'FRESH';
 | 
				
			||||||
 | 
					    if (toFormat !== orgFormat) {
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.beforeInlineConvert);
 | 
				
			||||||
 | 
					      rez = RConverter['to' + toFormat](rez);
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.afterInlineConvert, {
 | 
				
			||||||
 | 
					        file: sheetObjects[0].file,
 | 
				
			||||||
 | 
					        fmt: toFormat
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.applyTheme, {
 | 
				
			||||||
 | 
					      r: rez,
 | 
				
			||||||
 | 
					      theme: theme
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    _rezObj = new RTYPES[toFormat]().parseJSON(rez);
 | 
				
			||||||
 | 
					    targets = _expand(dst, theme);
 | 
				
			||||||
 | 
					    _.each(targets, function(t) {
 | 
				
			||||||
 | 
					      var ref;
 | 
				
			||||||
 | 
					      if (this.hasError() && opts.assert) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      t.final = _single.call(this, t, theme, targets);
 | 
				
			||||||
 | 
					      if ((ref = t.final) != null ? ref.fluenterror : void 0) {
 | 
				
			||||||
 | 
					        t.final.quit = opts.assert;
 | 
				
			||||||
 | 
					        this.err(t.final.fluenterror, t.final);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    results = {
 | 
				
			||||||
 | 
					      sheet: _rezObj,
 | 
				
			||||||
 | 
					      targets: targets,
 | 
				
			||||||
 | 
					      processed: targets
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (this.hasError() && !opts.assert) {
 | 
				
			||||||
 | 
					      this.reject(results);
 | 
				
			||||||
 | 
					    } else if (!this.hasError()) {
 | 
				
			||||||
 | 
					      this.resolve(results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Prepare for a BUILD run.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _prep = function(src, dst, opts) {
 | 
				
			||||||
 | 
					    var that;
 | 
				
			||||||
 | 
					    _opts.theme = (opts.theme && opts.theme.toLowerCase().trim()) || 'modern';
 | 
				
			||||||
 | 
					    _opts.prettify = opts.prettify === true;
 | 
				
			||||||
 | 
					    _opts.css = opts.css;
 | 
				
			||||||
 | 
					    _opts.pdf = opts.pdf;
 | 
				
			||||||
 | 
					    _opts.wrap = opts.wrap || 60;
 | 
				
			||||||
 | 
					    _opts.stitles = opts.sectionTitles;
 | 
				
			||||||
 | 
					    _opts.tips = opts.tips;
 | 
				
			||||||
 | 
					    _opts.errHandler = opts.errHandler;
 | 
				
			||||||
 | 
					    _opts.noTips = opts.noTips;
 | 
				
			||||||
 | 
					    _opts.debug = opts.debug;
 | 
				
			||||||
 | 
					    _opts.sort = opts.sort;
 | 
				
			||||||
 | 
					    that = this;
 | 
				
			||||||
 | 
					    _opts.onTransform = function(info) {
 | 
				
			||||||
 | 
					      that.stat(HMEVENT.afterTransform, info);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    _opts.beforeWrite = function(info) {
 | 
				
			||||||
 | 
					      that.stat(HMEVENT.beforeWrite, info);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    _opts.afterWrite = function(info) {
 | 
				
			||||||
 | 
					      that.stat(HMEVENT.afterWrite, info);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    (src.length > 1 && (!dst || !dst.length)) && dst.push(src.pop());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Generate a single target resume such as "out/rez.html" or "out/rez.doc".
 | 
				
			||||||
 | 
					  TODO: Refactor.
 | 
				
			||||||
 | 
					  @param targInfo Information for the target resume.
 | 
				
			||||||
 | 
					  @param theme A FRESHTheme or JRSTheme object.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _single = function(targInfo, theme, finished) {
 | 
				
			||||||
 | 
					    var e, ex, f, fName, fType, outFolder, ret, theFormat;
 | 
				
			||||||
 | 
					    ret = null;
 | 
				
			||||||
 | 
					    ex = null;
 | 
				
			||||||
 | 
					    f = targInfo.file;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!targInfo.fmt) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      fType = targInfo.fmt.outFormat;
 | 
				
			||||||
 | 
					      fName = PATH.basename(f, '.' + fType);
 | 
				
			||||||
 | 
					      theFormat = null;
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.beforeGenerate, {
 | 
				
			||||||
 | 
					        fmt: targInfo.fmt.outFormat,
 | 
				
			||||||
 | 
					        file: PATH.relative(process.cwd(), f)
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      _opts.targets = finished;
 | 
				
			||||||
 | 
					      if (targInfo.fmt.files && targInfo.fmt.files.length) {
 | 
				
			||||||
 | 
					        theFormat = _fmts.filter(function(fmt) {
 | 
				
			||||||
 | 
					          return fmt.name === targInfo.fmt.outFormat;
 | 
				
			||||||
 | 
					        })[0];
 | 
				
			||||||
 | 
					        MKDIRP.sync(PATH.dirname(f));
 | 
				
			||||||
 | 
					        ret = theFormat.gen.generate(_rezObj, f, _opts);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        theFormat = _fmts.filter(function(fmt) {
 | 
				
			||||||
 | 
					          return fmt.name === targInfo.fmt.outFormat;
 | 
				
			||||||
 | 
					        })[0];
 | 
				
			||||||
 | 
					        outFolder = PATH.dirname(f);
 | 
				
			||||||
 | 
					        MKDIRP.sync(outFolder);
 | 
				
			||||||
 | 
					        ret = theFormat.gen.generate(_rezObj, f, _opts);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      e = _error;
 | 
				
			||||||
 | 
					      ex = e;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.afterGenerate, {
 | 
				
			||||||
 | 
					      fmt: targInfo.fmt.outFormat,
 | 
				
			||||||
 | 
					      file: PATH.relative(process.cwd(), f),
 | 
				
			||||||
 | 
					      error: ex
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (ex) {
 | 
				
			||||||
 | 
					      if (ex.fluenterror) {
 | 
				
			||||||
 | 
					        ret = ex;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        ret = {
 | 
				
			||||||
 | 
					          fluenterror: HMSTATUS.generateError,
 | 
				
			||||||
 | 
					          inner: ex
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Ensure that user-specified outputs/targets are valid. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _verifyOutputs = function(targets, theme) {
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.verifyOutputs, {
 | 
				
			||||||
 | 
					      targets: targets,
 | 
				
			||||||
 | 
					      theme: theme
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return _.reject(targets.map(function(t) {
 | 
				
			||||||
 | 
					      var pathInfo;
 | 
				
			||||||
 | 
					      pathInfo = parsePath(t);
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        format: pathInfo.extname.substr(1)
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }), function(t) {
 | 
				
			||||||
 | 
					      return t.format === 'all' || theme.hasFormat(t.format);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Reinforce the chosen theme with "freebie" formats provided by HackMyResume.
 | 
				
			||||||
 | 
					  A "freebie" format is an output format such as JSON, YML, or PNG that can be
 | 
				
			||||||
 | 
					  generated directly from the resume model or from one of the theme's declared
 | 
				
			||||||
 | 
					  output formats. For example, the PNG format can be generated for any theme
 | 
				
			||||||
 | 
					  that declares an HTML format; the theme doesn't have to provide an explicit
 | 
				
			||||||
 | 
					  PNG template.
 | 
				
			||||||
 | 
					  @param theTheme A FRESHTheme or JRSTheme object.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _addFreebieFormats = function(theTheme) {
 | 
				
			||||||
 | 
					    theTheme.formats.json = theTheme.formats.json || {
 | 
				
			||||||
 | 
					      freebie: true,
 | 
				
			||||||
 | 
					      title: 'json',
 | 
				
			||||||
 | 
					      outFormat: 'json',
 | 
				
			||||||
 | 
					      pre: 'json',
 | 
				
			||||||
 | 
					      ext: 'json',
 | 
				
			||||||
 | 
					      path: null,
 | 
				
			||||||
 | 
					      data: null
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    theTheme.formats.yml = theTheme.formats.yml || {
 | 
				
			||||||
 | 
					      freebie: true,
 | 
				
			||||||
 | 
					      title: 'yaml',
 | 
				
			||||||
 | 
					      outFormat: 'yml',
 | 
				
			||||||
 | 
					      pre: 'yml',
 | 
				
			||||||
 | 
					      ext: 'yml',
 | 
				
			||||||
 | 
					      path: null,
 | 
				
			||||||
 | 
					      data: null
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (theTheme.formats.html && !theTheme.formats.png) {
 | 
				
			||||||
 | 
					      theTheme.formats.png = {
 | 
				
			||||||
 | 
					        freebie: true,
 | 
				
			||||||
 | 
					        title: 'png',
 | 
				
			||||||
 | 
					        outFormat: 'png',
 | 
				
			||||||
 | 
					        ext: 'yml',
 | 
				
			||||||
 | 
					        path: null,
 | 
				
			||||||
 | 
					        data: null
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Expand output files. For example, "foo.all" should be expanded to
 | 
				
			||||||
 | 
					  ["foo.html", "foo.doc", "foo.pdf", "etc"].
 | 
				
			||||||
 | 
					  @param dst An array of output files as specified by the user.
 | 
				
			||||||
 | 
					  @param theTheme A FRESHTheme or JRSTheme object.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _expand = function(dst, theTheme) {
 | 
				
			||||||
 | 
					    var destColl, targets;
 | 
				
			||||||
 | 
					    destColl = (dst && dst.length && dst) || [PATH.normalize('out/resume.all')];
 | 
				
			||||||
 | 
					    targets = [];
 | 
				
			||||||
 | 
					    destColl.forEach(function(t) {
 | 
				
			||||||
 | 
					      var fmat, pa, to;
 | 
				
			||||||
 | 
					      to = PATH.resolve(t);
 | 
				
			||||||
 | 
					      pa = parsePath(to);
 | 
				
			||||||
 | 
					      fmat = pa.extname || '.all';
 | 
				
			||||||
 | 
					      return targets.push.apply(targets, fmat === '.all' ? Object.keys(theTheme.formats).map(function(k) {
 | 
				
			||||||
 | 
					        var z;
 | 
				
			||||||
 | 
					        z = theTheme.formats[k];
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          file: to.replace(/all$/g, z.outFormat),
 | 
				
			||||||
 | 
					          fmt: z
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }) : [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          file: to,
 | 
				
			||||||
 | 
					          fmt: theTheme.getFormat(fmat.slice(1))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return targets;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Verify the specified theme name/path.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _verifyTheme = function(themeNameOrPath) {
 | 
				
			||||||
 | 
					    var exists, tFolder;
 | 
				
			||||||
 | 
					    tFolder = PATH.join(parsePath(require.resolve('fresh-themes')).dirname, '/themes/', themeNameOrPath);
 | 
				
			||||||
 | 
					    exists = require('path-exists').sync;
 | 
				
			||||||
 | 
					    if (!exists(tFolder)) {
 | 
				
			||||||
 | 
					      tFolder = PATH.resolve(themeNameOrPath);
 | 
				
			||||||
 | 
					      if (!exists(tFolder)) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          fluenterror: HMSTATUS.themeNotFound,
 | 
				
			||||||
 | 
					          data: _opts.theme
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return tFolder;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Load the specified theme, which could be either a FRESH theme or a JSON Resume
 | 
				
			||||||
 | 
					  theme.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _loadTheme = function(tFolder) {
 | 
				
			||||||
 | 
					    var theTheme;
 | 
				
			||||||
 | 
					    theTheme = _opts.theme.indexOf('jsonresume-theme-') > -1 ? new JRSTheme().open(tFolder) : new FRESHTheme().open(tFolder);
 | 
				
			||||||
 | 
					    _opts.themeObj = theTheme;
 | 
				
			||||||
 | 
					    return theTheme;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=build.js.map
 | 
				
			||||||
							
								
								
									
										115
									
								
								dist/verbs/convert.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								dist/verbs/convert.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Implementation of the 'convert' verb for HackMyResume.
 | 
				
			||||||
 | 
					@module verbs/convert
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var ConvertVerb, HMEVENT, HMSTATUS, ResumeFactory, Verb, _, _convert, _convertOne, chalk,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ResumeFactory = require('../core/resume-factory');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Verb = require('../verbs/verb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = ConvertVerb = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(ConvertVerb, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function ConvertVerb() {
 | 
				
			||||||
 | 
					      ConvertVerb.__super__.constructor.call(this, 'convert', _convert);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ConvertVerb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(Verb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Private workhorse method. Convert 0..N resumes between FRESH and JRS
 | 
				
			||||||
 | 
					  formats.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _convert = function(srcs, dst, opts) {
 | 
				
			||||||
 | 
					    var results;
 | 
				
			||||||
 | 
					    if (!srcs || !srcs.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.resumeNotFound, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!dst || !dst.length) {
 | 
				
			||||||
 | 
					      if (srcs.length === 1) {
 | 
				
			||||||
 | 
					        this.err(HMSTATUS.inputOutputParity, {
 | 
				
			||||||
 | 
					          quit: true
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else if (srcs.length === 2) {
 | 
				
			||||||
 | 
					        dst = dst || [];
 | 
				
			||||||
 | 
					        dst.push(srcs.pop());
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.err(HMSTATUS.inputOutputParity, {
 | 
				
			||||||
 | 
					          quit: true
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (srcs && dst && srcs.length && dst.length && srcs.length !== dst.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.inputOutputParity, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    results = _.map(srcs, function(src, idx) {
 | 
				
			||||||
 | 
					      var r;
 | 
				
			||||||
 | 
					      if (opts.assert && this.hasError()) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      r = _convertOne.call(this, src, dst, idx);
 | 
				
			||||||
 | 
					      if (r.fluenterror) {
 | 
				
			||||||
 | 
					        r.quit = opts.assert;
 | 
				
			||||||
 | 
					        this.err(r.fluenterror, r);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return r;
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    if (this.hasError() && !opts.assert) {
 | 
				
			||||||
 | 
					      this.reject(results);
 | 
				
			||||||
 | 
					    } else if (!this.hasError()) {
 | 
				
			||||||
 | 
					      this.resolve(results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Private workhorse method. Convert a single resume. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _convertOne = function(src, dst, idx) {
 | 
				
			||||||
 | 
					    var rinfo, s, srcFmt, targetFormat;
 | 
				
			||||||
 | 
					    rinfo = ResumeFactory.loadOne(src, {
 | 
				
			||||||
 | 
					      format: null,
 | 
				
			||||||
 | 
					      objectify: true
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (rinfo.fluenterror) {
 | 
				
			||||||
 | 
					      return rinfo;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    s = rinfo.rez;
 | 
				
			||||||
 | 
					    srcFmt = ((s.basics && s.basics.imp) || s.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH';
 | 
				
			||||||
 | 
					    targetFormat = srcFmt === 'JRS' ? 'FRESH' : 'JRS';
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.beforeConvert, {
 | 
				
			||||||
 | 
					      srcFile: rinfo.file,
 | 
				
			||||||
 | 
					      srcFmt: srcFmt,
 | 
				
			||||||
 | 
					      dstFile: dst[idx],
 | 
				
			||||||
 | 
					      dstFmt: targetFormat
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    s.saveAs(dst[idx], targetFormat);
 | 
				
			||||||
 | 
					    return s;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=convert.js.map
 | 
				
			||||||
							
								
								
									
										103
									
								
								dist/verbs/create.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								dist/verbs/create.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Implementation of the 'create' verb for HackMyResume.
 | 
				
			||||||
 | 
					@module verbs/create
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var CreateVerb, HMEVENT, HMSTATUS, MKDIRP, PATH, Verb, _, _create, _createOne, chalk,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MKDIRP = require('mkdirp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PATH = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Verb = require('../verbs/verb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = CreateVerb = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(CreateVerb, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function CreateVerb() {
 | 
				
			||||||
 | 
					      CreateVerb.__super__.constructor.call(this, 'new', _create);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return CreateVerb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(Verb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Create a new empty resume in either FRESH or JRS format. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _create = function(src, dst, opts) {
 | 
				
			||||||
 | 
					    var results;
 | 
				
			||||||
 | 
					    if (!src || !src.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.createNameMissing, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    results = _.map(src, function(t) {
 | 
				
			||||||
 | 
					      var r;
 | 
				
			||||||
 | 
					      if (opts.assert && this.hasError()) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      r = _createOne.call(this, t, opts);
 | 
				
			||||||
 | 
					      if (r.fluenterror) {
 | 
				
			||||||
 | 
					        r.quit = opts.assert;
 | 
				
			||||||
 | 
					        this.err(r.fluenterror, r);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return r;
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    if (this.hasError() && !opts.assert) {
 | 
				
			||||||
 | 
					      this.reject(this.errorCode);
 | 
				
			||||||
 | 
					    } else if (!this.hasError()) {
 | 
				
			||||||
 | 
					      this.resolve(results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Create a single new resume */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _createOne = function(t, opts) {
 | 
				
			||||||
 | 
					    var RezClass, newRez, ret, safeFmt;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      ret = null;
 | 
				
			||||||
 | 
					      safeFmt = opts.format.toUpperCase();
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.beforeCreate, {
 | 
				
			||||||
 | 
					        fmt: safeFmt,
 | 
				
			||||||
 | 
					        file: t
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      MKDIRP.sync(PATH.dirname(t));
 | 
				
			||||||
 | 
					      RezClass = require('../core/' + safeFmt.toLowerCase() + '-resume');
 | 
				
			||||||
 | 
					      newRez = RezClass["default"]();
 | 
				
			||||||
 | 
					      newRez.save(t);
 | 
				
			||||||
 | 
					      ret = newRez;
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      ret = {
 | 
				
			||||||
 | 
					        fluenterror: HMSTATUS.createError,
 | 
				
			||||||
 | 
					        inner: _error
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.afterCreate, {
 | 
				
			||||||
 | 
					        fmt: safeFmt,
 | 
				
			||||||
 | 
					        file: t,
 | 
				
			||||||
 | 
					        isError: ret.fluenterror
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return ret;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=create.js.map
 | 
				
			||||||
							
								
								
									
										106
									
								
								dist/verbs/peek.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								dist/verbs/peek.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Implementation of the 'peek' verb for HackMyResume.
 | 
				
			||||||
 | 
					@module verbs/peek
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var HMEVENT, HMSTATUS, PeekVerb, Verb, _, __, _peek, _peekOne, safeLoadJSON,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Verb = require('../verbs/verb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  __ = require('lodash');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  safeLoadJSON = require('../utils/safe-json-loader');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = PeekVerb = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(PeekVerb, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function PeekVerb() {
 | 
				
			||||||
 | 
					      PeekVerb.__super__.constructor.call(this, 'peek', _peek);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return PeekVerb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(Verb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Peek at a resume, resume section, or resume field. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _peek = function(src, dst, opts) {
 | 
				
			||||||
 | 
					    var objPath, results;
 | 
				
			||||||
 | 
					    if (!src || !src.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.resumeNotFound, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    objPath = (dst && dst[0]) || '';
 | 
				
			||||||
 | 
					    results = _.map(src, function(t) {
 | 
				
			||||||
 | 
					      var tgt;
 | 
				
			||||||
 | 
					      if (opts.assert && this.hasError()) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      tgt = _peekOne.call(this, t, objPath);
 | 
				
			||||||
 | 
					      if (tgt.error) {
 | 
				
			||||||
 | 
					        this.setError(tgt.error.fluenterror, tgt.error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return tgt;
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    if (this.hasError() && !opts.assert) {
 | 
				
			||||||
 | 
					      this.reject(this.errorCode);
 | 
				
			||||||
 | 
					    } else if (!this.hasError()) {
 | 
				
			||||||
 | 
					      this.resolve(results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Peek at a single resume, resume section, or resume field. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _peekOne = function(t, objPath) {
 | 
				
			||||||
 | 
					    var errCode, obj, pkgError, tgt;
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.beforePeek, {
 | 
				
			||||||
 | 
					      file: t,
 | 
				
			||||||
 | 
					      target: objPath
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    obj = safeLoadJSON(t);
 | 
				
			||||||
 | 
					    tgt = null;
 | 
				
			||||||
 | 
					    if (!obj.ex) {
 | 
				
			||||||
 | 
					      tgt = objPath ? __.get(obj.json, objPath) : obj.json;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pkgError = null;
 | 
				
			||||||
 | 
					    if (obj.ex) {
 | 
				
			||||||
 | 
					      errCode = obj.ex.operation === 'parse' ? HMSTATUS.parseError : HMSTATUS.readError;
 | 
				
			||||||
 | 
					      if (errCode === HMSTATUS.readError) {
 | 
				
			||||||
 | 
					        obj.ex.quiet = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      pkgError = {
 | 
				
			||||||
 | 
					        fluenterror: errCode,
 | 
				
			||||||
 | 
					        inner: obj.ex
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.afterPeek, {
 | 
				
			||||||
 | 
					      file: t,
 | 
				
			||||||
 | 
					      requested: objPath,
 | 
				
			||||||
 | 
					      target: obj.ex ? void 0 : tgt,
 | 
				
			||||||
 | 
					      error: pkgError
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      val: obj.ex ? void 0 : tgt,
 | 
				
			||||||
 | 
					      error: pkgError
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=peek.js.map
 | 
				
			||||||
							
								
								
									
										139
									
								
								dist/verbs/validate.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								dist/verbs/validate.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Implementation of the 'validate' verb for HackMyResume.
 | 
				
			||||||
 | 
					@module verbs/validate
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var FS, HMEVENT, HMSTATUS, ResumeFactory, SyntaxErrorEx, ValidateVerb, Verb, _, _validate, _validateOne, chalk, safeLoadJSON,
 | 
				
			||||||
 | 
					    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
 | 
				
			||||||
 | 
					    hasProp = {}.hasOwnProperty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FS = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ResumeFactory = require('../core/resume-factory');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SyntaxErrorEx = require('../utils/syntax-error-ex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  chalk = require('chalk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Verb = require('../verbs/verb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMSTATUS = require('../core/status-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _ = require('underscore');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  safeLoadJSON = require('../utils/safe-json-loader');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** An invokable resume validation command. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = ValidateVerb = (function(superClass) {
 | 
				
			||||||
 | 
					    extend(ValidateVerb, superClass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function ValidateVerb() {
 | 
				
			||||||
 | 
					      ValidateVerb.__super__.constructor.call(this, 'validate', _validate);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ValidateVerb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })(Verb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _validate = function(sources, unused, opts) {
 | 
				
			||||||
 | 
					    var results, schemas, validator;
 | 
				
			||||||
 | 
					    if (!sources || !sources.length) {
 | 
				
			||||||
 | 
					      this.err(HMSTATUS.resumeNotFoundAlt, {
 | 
				
			||||||
 | 
					        quit: true
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    validator = require('is-my-json-valid');
 | 
				
			||||||
 | 
					    schemas = {
 | 
				
			||||||
 | 
					      fresh: require('fresca'),
 | 
				
			||||||
 | 
					      jars: require('../core/resume.json')
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    results = _.map(sources, function(t) {
 | 
				
			||||||
 | 
					      var r;
 | 
				
			||||||
 | 
					      r = _validateOne.call(this, t, validator, schemas, opts);
 | 
				
			||||||
 | 
					      if (r.error) {
 | 
				
			||||||
 | 
					        this.err(r.error.fluenterror, r.error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return r;
 | 
				
			||||||
 | 
					    }, this);
 | 
				
			||||||
 | 
					    if (this.hasError() && !opts.assert) {
 | 
				
			||||||
 | 
					      this.reject(this.errorCode);
 | 
				
			||||||
 | 
					    } else if (!this.hasError()) {
 | 
				
			||||||
 | 
					      this.resolve(results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  Validate a single resume.
 | 
				
			||||||
 | 
					  @returns {
 | 
				
			||||||
 | 
					    file: <fileName>,
 | 
				
			||||||
 | 
					    isValid: <validFlag>,
 | 
				
			||||||
 | 
					    status: <validationStatus>,
 | 
				
			||||||
 | 
					    violations: <validationErrors>,
 | 
				
			||||||
 | 
					    schema: <schemaType>,
 | 
				
			||||||
 | 
					    error: <errorObject>
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _validateOne = function(t, validator, schemas, opts) {
 | 
				
			||||||
 | 
					    var errCode, obj, ret, validate;
 | 
				
			||||||
 | 
					    ret = {
 | 
				
			||||||
 | 
					      file: t,
 | 
				
			||||||
 | 
					      isValid: false,
 | 
				
			||||||
 | 
					      status: 'unknown',
 | 
				
			||||||
 | 
					      schema: '-----'
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      obj = safeLoadJSON(t);
 | 
				
			||||||
 | 
					      if (!obj.ex) {
 | 
				
			||||||
 | 
					        if (obj.json.basics) {
 | 
				
			||||||
 | 
					          ret.schema = 'jars';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ret.schema = 'fresh';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        validate = validator(schemas[ret.schema], {
 | 
				
			||||||
 | 
					          formats: {
 | 
				
			||||||
 | 
					            date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        ret.isValid = validate(obj.json);
 | 
				
			||||||
 | 
					        ret.status = ret.isValid ? 'valid' : 'invalid';
 | 
				
			||||||
 | 
					        if (!ret.isValid) {
 | 
				
			||||||
 | 
					          ret.violations = validate.errors;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (obj.ex.operation === 'parse') {
 | 
				
			||||||
 | 
					          errCode = HMSTATUS.parseError;
 | 
				
			||||||
 | 
					          ret.status = 'broken';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          errCode = HMSTATUS.readError;
 | 
				
			||||||
 | 
					          ret.status = 'missing';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ret.error = {
 | 
				
			||||||
 | 
					          fluenterror: errCode,
 | 
				
			||||||
 | 
					          inner: obj.ex.inner,
 | 
				
			||||||
 | 
					          quiet: errCode === HMSTATUS.readError
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (_error) {
 | 
				
			||||||
 | 
					      ret.error = {
 | 
				
			||||||
 | 
					        fluenterror: HMSTATUS.validateError,
 | 
				
			||||||
 | 
					        inner: _error
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.stat(HMEVENT.afterValidate, ret);
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=validate.js.map
 | 
				
			||||||
							
								
								
									
										118
									
								
								dist/verbs/verb.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								dist/verbs/verb.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Definition of the Verb class.
 | 
				
			||||||
 | 
					@module verbs/verb
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function() {
 | 
				
			||||||
 | 
					  var EVENTS, HMEVENT, Promise, Verb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EVENTS = require('events');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  HMEVENT = require('../core/event-codes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Promise = require('pinkie-promise');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					  An abstract invokable verb.
 | 
				
			||||||
 | 
					  Provides base class functionality for verbs. Provide common services such as
 | 
				
			||||||
 | 
					  error handling, event management, and promise support.
 | 
				
			||||||
 | 
					  @class Verb
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  module.exports = Verb = (function() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Constructor. Automatically called at creation. */
 | 
				
			||||||
 | 
					    function Verb(moniker, workhorse) {
 | 
				
			||||||
 | 
					      this.moniker = moniker;
 | 
				
			||||||
 | 
					      this.workhorse = workhorse;
 | 
				
			||||||
 | 
					      this.emitter = new EVENTS.EventEmitter();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Invoke the command. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.invoke = function() {
 | 
				
			||||||
 | 
					      var argsArray, that;
 | 
				
			||||||
 | 
					      this.stat(HMEVENT.begin, {
 | 
				
			||||||
 | 
					        cmd: this.moniker
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      argsArray = Array.prototype.slice.call(arguments);
 | 
				
			||||||
 | 
					      that = this;
 | 
				
			||||||
 | 
					      return this.promise = new Promise(function(res, rej) {
 | 
				
			||||||
 | 
					        that.resolve = res;
 | 
				
			||||||
 | 
					        that.reject = rej;
 | 
				
			||||||
 | 
					        that.workhorse.apply(that, argsArray);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Forward subscriptions to the event emitter. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.on = function() {
 | 
				
			||||||
 | 
					      return this.emitter.on.apply(this.emitter, arguments);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Fire an arbitrary event, scoped to "hmr:". */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.fire = function(evtName, payload) {
 | 
				
			||||||
 | 
					      payload = payload || {};
 | 
				
			||||||
 | 
					      payload.cmd = this.moniker;
 | 
				
			||||||
 | 
					      this.emitter.emit('hmr:' + evtName, payload);
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Handle an error condition. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.err = function(errorCode, payload, hot) {
 | 
				
			||||||
 | 
					      payload = payload || {};
 | 
				
			||||||
 | 
					      payload.sub = payload.fluenterror = errorCode;
 | 
				
			||||||
 | 
					      payload["throw"] = hot;
 | 
				
			||||||
 | 
					      this.setError(errorCode, payload);
 | 
				
			||||||
 | 
					      if (payload.quit) {
 | 
				
			||||||
 | 
					        this.reject(errorCode);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.fire('error', payload);
 | 
				
			||||||
 | 
					      if (hot) {
 | 
				
			||||||
 | 
					        throw payload;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Fire the 'hmr:status' error event. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.stat = function(subEvent, payload) {
 | 
				
			||||||
 | 
					      payload = payload || {};
 | 
				
			||||||
 | 
					      payload.sub = subEvent;
 | 
				
			||||||
 | 
					      this.fire('status', payload);
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Has an error occurred during this verb invocation? */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.hasError = function() {
 | 
				
			||||||
 | 
					      return this.errorCode || this.errorObj;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Associate error info with the invocation. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Verb.prototype.setError = function(code, obj) {
 | 
				
			||||||
 | 
					      this.errorCode = code;
 | 
				
			||||||
 | 
					      this.errorObj = obj;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Verb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//# sourceMappingURL=verb.js.map
 | 
				
			||||||
							
								
								
									
										96
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								package.json
									
									
									
									
									
								
							@@ -1,49 +1,107 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "fluentcv",
 | 
					  "name": "hackmyresume",
 | 
				
			||||||
  "version": "0.7.1",
 | 
					  "version": "1.8.0",
 | 
				
			||||||
  "description": "Generate beautiful, targeted resumes from your command line, shell, or in Javascript with Node.js.",
 | 
					  "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.",
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
    "type": "git",
 | 
					    "type": "git",
 | 
				
			||||||
    "url": "https://github.com/fluentdesk/fluentcv.git"
 | 
					    "url": "https://github.com/hacksalot/HackMyResume.git"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "test": "grunt clean:test && mocha",
 | 
				
			||||||
 | 
					    "grunt": "grunt"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "keywords": [
 | 
					  "keywords": [
 | 
				
			||||||
    "resume",
 | 
					    "resume",
 | 
				
			||||||
    "CV",
 | 
					    "CV",
 | 
				
			||||||
    "portfolio",
 | 
					    "portfolio",
 | 
				
			||||||
    "Markdown"
 | 
					    "employment",
 | 
				
			||||||
 | 
					    "career",
 | 
				
			||||||
 | 
					    "Markdown",
 | 
				
			||||||
 | 
					    "JSON",
 | 
				
			||||||
 | 
					    "Word",
 | 
				
			||||||
 | 
					    "PDF",
 | 
				
			||||||
 | 
					    "YAML",
 | 
				
			||||||
 | 
					    "HTML",
 | 
				
			||||||
 | 
					    "LaTeX",
 | 
				
			||||||
 | 
					    "CLI",
 | 
				
			||||||
 | 
					    "Handlebars",
 | 
				
			||||||
 | 
					    "Underscore",
 | 
				
			||||||
 | 
					    "template"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "author": "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)",
 | 
				
			||||||
 | 
					  "contributors": [
 | 
				
			||||||
 | 
					    "aruberto (https://github.com/aruberto)",
 | 
				
			||||||
 | 
					    "jjanusch (https://github.com/driftdev)",
 | 
				
			||||||
 | 
					    "robertmain (https://github.com/robertmain)",
 | 
				
			||||||
 | 
					    "tomheon (https://github.com/tomheon)",
 | 
				
			||||||
 | 
					    "zhuangya (https://github.com/zhuangya)",
 | 
				
			||||||
 | 
					    "hacksalot <hacksalot@indevious.com> (https://github.com/hacksalot)"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "author": "James M. Devlin",
 | 
					 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
  "preferGlobal": "true",
 | 
					  "preferGlobal": "true",
 | 
				
			||||||
  "bugs": {
 | 
					  "bugs": {
 | 
				
			||||||
    "url": "https://github.com/fluentdesk/fluentcv/issues"
 | 
					    "url": "https://github.com/JuanCanham/HackMyResume/issues"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "main": "src/fluentcmd.js",
 | 
					 | 
				
			||||||
  "bin": {
 | 
					  "bin": {
 | 
				
			||||||
    "fluentcv": "src/index.js"
 | 
					    "hackmyresume": "dist/cli/index.js"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "homepage": "https://github.com/fluentdesk/fluentcv",
 | 
					  "main": "dist/index.js",
 | 
				
			||||||
 | 
					  "homepage": "https://github.com/JuanCanham/HackMyResume",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "fluent-themes": "0.1.0-beta",
 | 
					    "chalk": "^1.1.1",
 | 
				
			||||||
    "fs-extra": "^0.24.0",
 | 
					    "commander": "^2.9.0",
 | 
				
			||||||
 | 
					    "copy": "^0.1.3",
 | 
				
			||||||
 | 
					    "escape-latex": "^0.1.2",
 | 
				
			||||||
 | 
					    "extend": "^3.0.0",
 | 
				
			||||||
 | 
					    "fresca": "~0.6.0",
 | 
				
			||||||
 | 
					    "fresh-jrs-converter": "^0.2.2",
 | 
				
			||||||
 | 
					    "fresh-resume-starter": "^0.2.2",
 | 
				
			||||||
 | 
					    "fresh-themes": "git+https://git.juancanham.com/JuanCanham/fresh-themes.git#feature/certifications",
 | 
				
			||||||
 | 
					    "fs-extra": "^0.26.4",
 | 
				
			||||||
 | 
					    "handlebars": "^4.0.5",
 | 
				
			||||||
    "html": "0.0.10",
 | 
					    "html": "0.0.10",
 | 
				
			||||||
    "is-my-json-valid": "^2.12.2",
 | 
					    "is-my-json-valid": "^2.12.4",
 | 
				
			||||||
    "jst": "0.0.13",
 | 
					    "json-lint": "^0.1.0",
 | 
				
			||||||
 | 
					    "jsonlint": "^1.6.2",
 | 
				
			||||||
 | 
					    "lodash": "^3.10.1",
 | 
				
			||||||
    "marked": "^0.3.5",
 | 
					    "marked": "^0.3.5",
 | 
				
			||||||
    "minimist": "^1.2.0",
 | 
					 | 
				
			||||||
    "mkdirp": "^0.5.1",
 | 
					    "mkdirp": "^0.5.1",
 | 
				
			||||||
    "moment": "^2.10.6",
 | 
					    "moment": "^2.11.1",
 | 
				
			||||||
 | 
					    "parse-filepath": "^0.6.3",
 | 
				
			||||||
 | 
					    "path-exists": "^2.1.0",
 | 
				
			||||||
 | 
					    "pinkie-promise": "^2.0.0",
 | 
				
			||||||
 | 
					    "printf": "^0.2.3",
 | 
				
			||||||
 | 
					    "recursive-readdir-sync": "^1.0.6",
 | 
				
			||||||
 | 
					    "simple-html-tokenizer": "^0.2.1",
 | 
				
			||||||
 | 
					    "slash": "^1.0.0",
 | 
				
			||||||
 | 
					    "string-padding": "^1.0.2",
 | 
				
			||||||
 | 
					    "string.prototype.endswith": "^0.2.0",
 | 
				
			||||||
 | 
					    "string.prototype.startswith": "^0.2.0",
 | 
				
			||||||
 | 
					    "traverse": "^0.6.6",
 | 
				
			||||||
    "underscore": "^1.8.3",
 | 
					    "underscore": "^1.8.3",
 | 
				
			||||||
    "wkhtmltopdf": "^0.1.5",
 | 
					    "word-wrap": "^1.1.0",
 | 
				
			||||||
    "xml-escape": "^1.0.0",
 | 
					    "xml-escape": "^1.0.0",
 | 
				
			||||||
    "yamljs": "^0.2.4"
 | 
					    "yamljs": "^0.2.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "chai": "*",
 | 
					    "chai": "*",
 | 
				
			||||||
 | 
					    "dir-compare": "0.0.2",
 | 
				
			||||||
 | 
					    "fresh-test-resumes": "^0.7.0",
 | 
				
			||||||
    "grunt": "*",
 | 
					    "grunt": "*",
 | 
				
			||||||
 | 
					    "grunt-cli": "^0.1.13",
 | 
				
			||||||
 | 
					    "grunt-contrib-clean": "^0.7.0",
 | 
				
			||||||
 | 
					    "grunt-contrib-coffee": "^0.13.0",
 | 
				
			||||||
 | 
					    "grunt-contrib-copy": "^0.8.2",
 | 
				
			||||||
 | 
					    "grunt-contrib-jshint": "^0.11.3",
 | 
				
			||||||
 | 
					    "grunt-contrib-yuidoc": "^0.10.0",
 | 
				
			||||||
 | 
					    "grunt-jsdoc": "^1.1.0",
 | 
				
			||||||
    "grunt-simple-mocha": "*",
 | 
					    "grunt-simple-mocha": "*",
 | 
				
			||||||
    "is-my-json-valid": "^2.12.2",
 | 
					    "jsonresume-theme-boilerplate": "^0.1.2",
 | 
				
			||||||
 | 
					    "jsonresume-theme-classy": "^1.0.9",
 | 
				
			||||||
 | 
					    "jsonresume-theme-modern": "0.0.18",
 | 
				
			||||||
 | 
					    "jsonresume-theme-sceptile": "^1.0.5",
 | 
				
			||||||
    "mocha": "*",
 | 
					    "mocha": "*",
 | 
				
			||||||
    "resample": "fluentdesk/resample"
 | 
					    "resample": "fluentdesk/resample",
 | 
				
			||||||
 | 
					    "stripcolorcodes": "^0.1.0"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								src/cli/analyze.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/cli/analyze.hbs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					{{style "SECTIONS (" "bold"}}{{style totals.numSections "white" }}{{style ")" "bold"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        employment:     {{v totals.totals.employment "-" 2 "bold" }}
 | 
				
			||||||
 | 
					          projects:     {{v totals.totals.projects "-" 2 "bold" }}
 | 
				
			||||||
 | 
					         education:     {{v totals.totals.education "-" 2 "bold" }}
 | 
				
			||||||
 | 
					           service:     {{v totals.totals.service "-" 2 "bold" }}
 | 
				
			||||||
 | 
					            skills:     {{v totals.totals.skills "-" 2 "bold" }}
 | 
				
			||||||
 | 
					           writing:     {{v totals.totals.writing "-" 2 "bold" }}
 | 
				
			||||||
 | 
					          speaking:     {{v totals.totals.speaking "-" 2 "bold" }}
 | 
				
			||||||
 | 
					           reading:     {{v totals.totals.reading "-" 2 "bold" }}
 | 
				
			||||||
 | 
					            social:     {{v totals.totals.social "-" 2 "bold" }}
 | 
				
			||||||
 | 
					        references:     {{v totals.totals.references "-" 2 "bold" }}
 | 
				
			||||||
 | 
					      testimonials:     {{v totals.totals.testimonials "-" 2 "bold" }}
 | 
				
			||||||
 | 
					         languages:     {{v totals.totals.languages "-" 2 "bold" }}
 | 
				
			||||||
 | 
					         interests:     {{v totals.totals.interests "-" 2 "bold" }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{style "COVERAGE (" "bold"}}{{style coverage.pct "white"}}{{style ")" "bold"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Total Days:  {{v coverage.duration.total "-" 5 "bold" }}
 | 
				
			||||||
 | 
					          Employed:  {{v coverage.duration.work "-" 5 "bold" }}
 | 
				
			||||||
 | 
					              Gaps:  {{v coverage.gaps.length "-" 5 "bold" }}  [{{#if coverage.gaps.length }}{{#each coverage.gaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}]
 | 
				
			||||||
 | 
					          Overlaps:  {{v coverage.overlaps.length "-" 5 "bold" }}  [{{#if coverage.overlaps.length }}{{#each coverage.overlaps }}{{#unless @first}} {{/unless}}{{gapLength duration }}{{/each}}{{/if}}]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{style "KEYWORDS (" "bold"}}{{style keywords.length "white" }}{{style ")" "bold"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{#each keywords }}{{{pad name 18}}}:  {{v count "-" 5 "bold"}} mention{{#isPlural count}}s{{/isPlural}}
 | 
				
			||||||
 | 
					{{/each}}
 | 
				
			||||||
 | 
					    -------------------------------
 | 
				
			||||||
 | 
					{{v keywords.length "0" 9 "bold"}} {{style "KEYWORDS" "bold"}}   {{v keywords.totalKeywords "0" 5 "bold"}} {{style "mentions" "bold"}}
 | 
				
			||||||
							
								
								
									
										232
									
								
								src/cli/error.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								src/cli/error.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,232 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Error-handling routines for HackMyResume.
 | 
				
			||||||
 | 
					@module cli/error
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HMSTATUS = require '../core/status-codes'
 | 
				
			||||||
 | 
					PKG = require '../../package.json'
 | 
				
			||||||
 | 
					FS = require 'fs'
 | 
				
			||||||
 | 
					FCMD = require '../index'
 | 
				
			||||||
 | 
					PATH = require 'path'
 | 
				
			||||||
 | 
					WRAP = require 'word-wrap'
 | 
				
			||||||
 | 
					M2C = require '../utils/md2chalk'
 | 
				
			||||||
 | 
					chalk = require 'chalk'
 | 
				
			||||||
 | 
					extend = require 'extend'
 | 
				
			||||||
 | 
					YAML = require 'yamljs'
 | 
				
			||||||
 | 
					printf = require 'printf'
 | 
				
			||||||
 | 
					SyntaxErrorEx = require '../utils/syntax-error-ex'
 | 
				
			||||||
 | 
					require 'string.prototype.startswith'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###* Error handler for HackMyResume. All errors are handled here.
 | 
				
			||||||
 | 
					@class ErrorHandler ###
 | 
				
			||||||
 | 
					ErrorHandler = module.exports =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init: ( debug, assert, silent ) ->
 | 
				
			||||||
 | 
					    @debug = debug
 | 
				
			||||||
 | 
					    @assert = assert
 | 
				
			||||||
 | 
					    @silent = silent
 | 
				
			||||||
 | 
					    @msgs = require('./msg').errors
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  err: ( ex, shouldExit ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Short-circuit logging output if --silent is on
 | 
				
			||||||
 | 
					    o = if @silent then () -> else _defaultLog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Special case; can probably be removed.
 | 
				
			||||||
 | 
					    throw ex if ex.pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Load error messages
 | 
				
			||||||
 | 
					    @msgs = @msgs || require('./msg').errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Handle packaged HMR exceptions
 | 
				
			||||||
 | 
					    if ex.fluenterror
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Output the error message
 | 
				
			||||||
 | 
					      objError = assembleError.call @, ex
 | 
				
			||||||
 | 
					      o( @[ 'format_' + objError.etype ]( objError.msg ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Output the stack (sometimes)
 | 
				
			||||||
 | 
					      if objError.withStack
 | 
				
			||||||
 | 
					        stack = ex.stack || (ex.inner && ex.inner.stack);
 | 
				
			||||||
 | 
					        stack && o( chalk.gray( stack ) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Quit if necessary
 | 
				
			||||||
 | 
					      if shouldExit
 | 
				
			||||||
 | 
					        if @debug
 | 
				
			||||||
 | 
					          o chalk.cyan('Exiting with error code ' + ex.fluenterror.toString())
 | 
				
			||||||
 | 
					        if @assert
 | 
				
			||||||
 | 
					          ex.pass = true
 | 
				
			||||||
 | 
					          throw ex
 | 
				
			||||||
 | 
					        process.exit ex.fluenterror
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Handle raw exceptions
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      o ex
 | 
				
			||||||
 | 
					      stackTrace = ex.stack || (ex.inner && ex.inner.stack)
 | 
				
			||||||
 | 
					      if stackTrace && this.debug
 | 
				
			||||||
 | 
					        o M2C(ex.stack || ex.inner.stack, 'gray')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  format_error: ( msg ) ->
 | 
				
			||||||
 | 
					    msg = msg || ''
 | 
				
			||||||
 | 
					    chalk.red.bold( if msg.toUpperCase().startsWith('ERROR:') then msg else 'Error: ' + msg )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  format_warning: ( brief, msg ) ->
 | 
				
			||||||
 | 
					    chalk.yellow(brief) + chalk.yellow(msg || '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  format_custom: ( msg ) -> msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_defaultLog = () -> console.log.apply console.log, arguments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					assembleError = ( ex ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  msg = ''
 | 
				
			||||||
 | 
					  withStack = false
 | 
				
			||||||
 | 
					  quit = false
 | 
				
			||||||
 | 
					  etype = 'warning'
 | 
				
			||||||
 | 
					  withStack = true if @debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch ex.fluenterror
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.themeNotFound
 | 
				
			||||||
 | 
					      msg = printf( M2C( this.msgs.themeNotFound.msg, 'yellow' ), ex.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.copyCSS
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.copyCSS.msg, 'red' )
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.resumeNotFound
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.resumeNotFound.msg, 'yellow' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.missingCommand
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.missingCommand.msg + " (", 'yellow');
 | 
				
			||||||
 | 
					      msg += Object.keys( FCMD.verbs ).map( (v, idx, ar) ->
 | 
				
			||||||
 | 
					        return ( if idx == ar.length - 1 then chalk.yellow('or ') else '') +
 | 
				
			||||||
 | 
					          chalk.yellow.bold(v.toUpperCase());
 | 
				
			||||||
 | 
					      ).join( chalk.yellow(', ')) + chalk.yellow(").\n\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      msg += chalk.gray(FS.readFileSync(
 | 
				
			||||||
 | 
					        PATH.resolve(__dirname, '../cli/use.txt'), 'utf8' ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.invalidCommand
 | 
				
			||||||
 | 
					      msg = printf( M2C( this.msgs.invalidCommand.msg, 'yellow'), ex.attempted )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.resumeNotFoundAlt
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.resumeNotFoundAlt.msg, 'yellow' )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.inputOutputParity
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.inputOutputParity.msg )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.createNameMissing
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.createNameMissing.msg )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.pdfGeneration
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.pdfGeneration.msg, 'bold' )
 | 
				
			||||||
 | 
					      msg += chalk.red('\n' + ex.inner) if ex.inner
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.invalid
 | 
				
			||||||
 | 
					      msg = M2C( this.msgs.invalid.msg, 'red' )
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.generateError
 | 
				
			||||||
 | 
					      msg = (ex.inner && ex.inner.toString()) || ex
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.fileSaveError
 | 
				
			||||||
 | 
					      msg = printf( M2C( this.msgs.fileSaveError.msg ), (ex.inner || ex).toString() )
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.invalidFormat
 | 
				
			||||||
 | 
					      ex.data.forEach( (d) ->
 | 
				
			||||||
 | 
					        msg += printf( M2C( this.msgs.invalidFormat.msg, 'bold' ),
 | 
				
			||||||
 | 
					          ex.theme.name.toUpperCase(), d.format.toUpperCase())
 | 
				
			||||||
 | 
					      , @);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.missingParam
 | 
				
			||||||
 | 
					      msg = printf(M2C( this.msgs.missingParam.msg ), ex.expected, ex.helper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.invalidHelperUse
 | 
				
			||||||
 | 
					      msg = printf( M2C( this.msgs.invalidHelperUse.msg ), ex.helper )
 | 
				
			||||||
 | 
					      if ex.error
 | 
				
			||||||
 | 
					        msg += '\n--> ' + assembleError.call( this, extend( true, {}, ex, {fluenterror: ex.error} )).msg;
 | 
				
			||||||
 | 
					        #msg += printf( '\n--> ' + M2C( this.msgs.invalidParamCount.msg ), ex.expected );
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					      etype = 'warning'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.notOnPath
 | 
				
			||||||
 | 
					      msg = printf( M2C(this.msgs.notOnPath.msg, 'bold'), ex.engine)
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.readError
 | 
				
			||||||
 | 
					      if !ex.quiet
 | 
				
			||||||
 | 
					        console.error(printf( M2C(this.msgs.readError.msg, 'red'), ex.file))
 | 
				
			||||||
 | 
					      msg = ex.inner.toString()
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.mixedMerge
 | 
				
			||||||
 | 
					      msg = M2C this.msgs.mixedMerge.msg
 | 
				
			||||||
 | 
					      quit = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.invokeTemplate
 | 
				
			||||||
 | 
					      msg = M2C this.msgs.invokeTemplate.msg, 'red'
 | 
				
			||||||
 | 
					      msg += M2C( '\n' + WRAP(ex.inner.toString(), { width: 60, indent: '   ' }), 'gray' );
 | 
				
			||||||
 | 
					      etype = 'custom'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.compileTemplate
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.themeLoad
 | 
				
			||||||
 | 
					      msg = M2C( printf( this.msgs.themeLoad.msg, ex.attempted.toUpperCase() ), 'red');
 | 
				
			||||||
 | 
					      if ex.inner && ex.inner.fluenterror
 | 
				
			||||||
 | 
					        msg += M2C('\nError: ', 'red') + assembleError.call( this, ex.inner ).msg
 | 
				
			||||||
 | 
					      quit = true
 | 
				
			||||||
 | 
					      etype = 'custom'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.parseError
 | 
				
			||||||
 | 
					      if SyntaxErrorEx.is ex.inner
 | 
				
			||||||
 | 
					        console.error printf( M2C(this.msgs.readError.msg, 'red'), ex.file )
 | 
				
			||||||
 | 
					        se = new SyntaxErrorEx ex, ex.raw
 | 
				
			||||||
 | 
					        if se.line? and se.col?
 | 
				
			||||||
 | 
					          msg = printf M2C( this.msgs.parseError.msg[0], 'red' ), se.line, se.col
 | 
				
			||||||
 | 
					        else if se.line?
 | 
				
			||||||
 | 
					          msg = printf M2C( this.msgs.parseError.msg[1], 'red' ), se.line
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          msg = M2C @msgs.parseError.msg[2], 'red'
 | 
				
			||||||
 | 
					      else if ex.inner && ex.inner.line? && ex.inner.col?
 | 
				
			||||||
 | 
					        msg = printf( M2C( this.msgs.parseError.msg[0], 'red' ), ex.inner.line, ex.inner.col)
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        msg = ex
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.createError
 | 
				
			||||||
 | 
					      # inner.code could be EPERM, EACCES, etc
 | 
				
			||||||
 | 
					      msg = printf M2C( this.msgs.createError.msg ), ex.inner.path
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    when HMSTATUS.validateError
 | 
				
			||||||
 | 
					      msg = printf M2C( @msgs.validateError.msg ), ex.inner.toString()
 | 
				
			||||||
 | 
					      etype = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  msg: msg              # The error message to display
 | 
				
			||||||
 | 
					  withStack: withStack  # Whether to include the stack
 | 
				
			||||||
 | 
					  quit: quit
 | 
				
			||||||
 | 
					  etype: etype
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/cli/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/cli/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					#! /usr/bin/env node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					Command-line interface (CLI) for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module index.js
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('./main')( process.argv );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					catch( ex ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  require('./error').err( ex, true );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										349
									
								
								src/cli/main.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								src/cli/main.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,349 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the `main` function.
 | 
				
			||||||
 | 
					@module cli/main
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HMR = require '../index'
 | 
				
			||||||
 | 
					PKG = require '../../package.json'
 | 
				
			||||||
 | 
					FS = require 'fs'
 | 
				
			||||||
 | 
					EXTEND = require 'extend'
 | 
				
			||||||
 | 
					chalk = require 'chalk'
 | 
				
			||||||
 | 
					PATH = require 'path'
 | 
				
			||||||
 | 
					HMSTATUS = require '../core/status-codes'
 | 
				
			||||||
 | 
					HME = require '../core/event-codes'
 | 
				
			||||||
 | 
					safeLoadJSON = require '../utils/safe-json-loader'
 | 
				
			||||||
 | 
					StringUtils = require '../utils/string.js'
 | 
				
			||||||
 | 
					_ = require 'underscore'
 | 
				
			||||||
 | 
					OUTPUT = require './out'
 | 
				
			||||||
 | 
					PAD = require 'string-padding'
 | 
				
			||||||
 | 
					Command = require('commander').Command
 | 
				
			||||||
 | 
					M2C = require '../utils/md2chalk'
 | 
				
			||||||
 | 
					printf = require 'printf'
 | 
				
			||||||
 | 
					_opts = { }
 | 
				
			||||||
 | 
					_title = chalk.white.bold('\n*** HackMyResume v' +PKG.version+ ' ***')
 | 
				
			||||||
 | 
					_out = new OUTPUT( _opts )
 | 
				
			||||||
 | 
					_err = require('./error')
 | 
				
			||||||
 | 
					_exitCallback = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					A callable implementation of the HackMyResume CLI. Encapsulates the command
 | 
				
			||||||
 | 
					line interface as a single method accepting a parameter array.
 | 
				
			||||||
 | 
					@alias module:cli/main.main
 | 
				
			||||||
 | 
					@param rawArgs {Array} An array of command-line parameters. Will either be
 | 
				
			||||||
 | 
					process.argv (in production) or custom parameters (in test).
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					main = module.exports = ( rawArgs, exitCallback ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  initInfo = initialize( rawArgs, exitCallback )
 | 
				
			||||||
 | 
					  args = initInfo.args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the top-level (application) command...
 | 
				
			||||||
 | 
					  program = new Command('hackmyresume')
 | 
				
			||||||
 | 
					    .version(PKG.version)
 | 
				
			||||||
 | 
					    .description(chalk.yellow.bold('*** HackMyResume ***'))
 | 
				
			||||||
 | 
					    .option('-s --silent', 'Run in silent mode')
 | 
				
			||||||
 | 
					    .option('--no-color', 'Disable colors')
 | 
				
			||||||
 | 
					    .option('--color', 'Enable colors')
 | 
				
			||||||
 | 
					    .option('-d --debug', 'Enable diagnostics', false)
 | 
				
			||||||
 | 
					    .option('-a --assert', 'Treat warnings as errors', false)
 | 
				
			||||||
 | 
					    .option('-v --version', 'Show the version')
 | 
				
			||||||
 | 
					    .allowUnknownOption();
 | 
				
			||||||
 | 
					    program.jsonArgs = initInfo.options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the NEW command
 | 
				
			||||||
 | 
					  program
 | 
				
			||||||
 | 
					    .command 'new'
 | 
				
			||||||
 | 
					    .arguments '<sources...>'
 | 
				
			||||||
 | 
					    .option '-f --format <fmt>', 'FRESH or JRS format', 'FRESH'
 | 
				
			||||||
 | 
					    .alias 'create'
 | 
				
			||||||
 | 
					    .description 'Create resume(s) in FRESH or JSON RESUME format.'
 | 
				
			||||||
 | 
					    .action (( sources ) ->
 | 
				
			||||||
 | 
					      execute.call( this, sources, [], this.opts(), logMsg)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the VALIDATE command
 | 
				
			||||||
 | 
					  program
 | 
				
			||||||
 | 
					    .command('validate')
 | 
				
			||||||
 | 
					    .arguments('<sources...>')
 | 
				
			||||||
 | 
					    .description('Validate a resume in FRESH or JSON RESUME format.')
 | 
				
			||||||
 | 
					    .action((sources) ->
 | 
				
			||||||
 | 
					      execute.call( this, sources, [], this.opts(), logMsg)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the CONVERT command
 | 
				
			||||||
 | 
					  program
 | 
				
			||||||
 | 
					    .command('convert')
 | 
				
			||||||
 | 
					    .description('Convert a resume to/from FRESH or JSON RESUME format.')
 | 
				
			||||||
 | 
					    .action(->
 | 
				
			||||||
 | 
					      x = splitSrcDest.call( this );
 | 
				
			||||||
 | 
					      execute.call( this, x.src, x.dst, this.opts(), logMsg)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the ANALYZE command
 | 
				
			||||||
 | 
					  program
 | 
				
			||||||
 | 
					    .command('analyze')
 | 
				
			||||||
 | 
					    .arguments('<sources...>')
 | 
				
			||||||
 | 
					    .description('Analyze one or more resumes.')
 | 
				
			||||||
 | 
					    .action(( sources ) ->
 | 
				
			||||||
 | 
					      execute.call( this, sources, [], this.opts(), logMsg)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the PEEK command
 | 
				
			||||||
 | 
					  program
 | 
				
			||||||
 | 
					    .command('peek')
 | 
				
			||||||
 | 
					    .arguments('<sources...>')
 | 
				
			||||||
 | 
					    .description('Peek at a resume field or section')
 | 
				
			||||||
 | 
					    .action(( sources, sectionOrField ) ->
 | 
				
			||||||
 | 
					      dst = if (sources && sources.length > 1) then [sources.pop()] else []
 | 
				
			||||||
 | 
					      execute.call( this, sources, dst, this.opts(), logMsg)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the BUILD command
 | 
				
			||||||
 | 
					  program
 | 
				
			||||||
 | 
					    .command('build')
 | 
				
			||||||
 | 
					    .alias('generate')
 | 
				
			||||||
 | 
					    .option('-t --theme <theme>', 'Theme name or path')
 | 
				
			||||||
 | 
					    .option('-n --no-prettify', 'Disable HTML prettification', true)
 | 
				
			||||||
 | 
					    .option('-c --css <option>', 'CSS linking / embedding')
 | 
				
			||||||
 | 
					    .option('-p --pdf <engine>', 'PDF generation engine')
 | 
				
			||||||
 | 
					    .option('--no-sort', 'Sort resume sections by date', false)
 | 
				
			||||||
 | 
					    .option('--tips', 'Display theme tips and warnings.', false)
 | 
				
			||||||
 | 
					    .description('Generate resume to multiple formats')
 | 
				
			||||||
 | 
					    .action(( sources, targets, options ) ->
 | 
				
			||||||
 | 
					      x = splitSrcDest.call( this );
 | 
				
			||||||
 | 
					      execute.call( this, x.src, x.dst, this.opts(), logMsg)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  program.parse( args )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if !program.args.length
 | 
				
			||||||
 | 
					    throw fluenterror: 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Massage command-line args and setup Commander.js. ###
 | 
				
			||||||
 | 
					initialize = ( ar, exitCallback ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _exitCallback = exitCallback || process.exit
 | 
				
			||||||
 | 
					  o = initOptions ar
 | 
				
			||||||
 | 
					  o.silent || logMsg( _title )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Emit debug prelude if --debug was specified
 | 
				
			||||||
 | 
					  if o.debug
 | 
				
			||||||
 | 
					    _out.log(chalk.cyan('The -d or --debug switch was specified. DEBUG mode engaged.'))
 | 
				
			||||||
 | 
					    _out.log('')
 | 
				
			||||||
 | 
					    _out.log(chalk.cyan(PAD('  Platform:',25, null, PAD.RIGHT)) + chalk.cyan.bold( if process.platform == 'win32' then 'windows' else process.platform ))
 | 
				
			||||||
 | 
					    _out.log(chalk.cyan(PAD('  Node.js:',25, null, PAD.RIGHT)) + chalk.cyan.bold( process.version ))
 | 
				
			||||||
 | 
					    _out.log(chalk.cyan(PAD('  HackMyResume:',25, null, PAD.RIGHT)) + chalk.cyan.bold('v' + PKG.version ))
 | 
				
			||||||
 | 
					    _out.log(chalk.cyan(PAD('  FRESCA:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies.fresca ))
 | 
				
			||||||
 | 
					    #_out.log(chalk.cyan(PAD('  fresh-themes:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-themes'] ))
 | 
				
			||||||
 | 
					    #_out.log(chalk.cyan(PAD('  fresh-jrs-converter:',25, null, PAD.RIGHT)) + chalk.cyan.bold( PKG.dependencies['fresh-jrs-converter'] ))
 | 
				
			||||||
 | 
					    _out.log('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _err.init o.debug, o.assert, o.silent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Handle invalid verbs here (a bit easier here than in commander.js)...
 | 
				
			||||||
 | 
					  if o.verb && !HMR.verbs[ o.verb ] && !HMR.alias[ o.verb ]
 | 
				
			||||||
 | 
					    _err.err fluenterror: HMSTATUS.invalidCommand, quit: true, attempted: o.orgVerb, true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Override the .missingArgument behavior
 | 
				
			||||||
 | 
					  Command.prototype.missingArgument = (name) ->
 | 
				
			||||||
 | 
					    _err.err
 | 
				
			||||||
 | 
					      fluenterror:
 | 
				
			||||||
 | 
					        if this.name() != 'new'
 | 
				
			||||||
 | 
					        then HMSTATUS.resumeNotFound
 | 
				
			||||||
 | 
					        else HMSTATUS.createNameMissing
 | 
				
			||||||
 | 
					      , true
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Override the .helpInformation behavior
 | 
				
			||||||
 | 
					  Command.prototype.helpInformation = ->
 | 
				
			||||||
 | 
					    manPage = FS.readFileSync(
 | 
				
			||||||
 | 
					      PATH.join(__dirname, 'use.txt'), 'utf8' )
 | 
				
			||||||
 | 
					    return chalk.green.bold(manPage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    args: o.args,
 | 
				
			||||||
 | 
					    options: o.json
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Init options prior to setting up command infrastructure. ###
 | 
				
			||||||
 | 
					initOptions = ( ar ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  oVerb
 | 
				
			||||||
 | 
					  verb = ''
 | 
				
			||||||
 | 
					  args = ar.slice()
 | 
				
			||||||
 | 
					  cleanArgs = args.slice( 2 )
 | 
				
			||||||
 | 
					  oJSON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if cleanArgs.length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Support case-insensitive sub-commands (build, generate, validate, etc)
 | 
				
			||||||
 | 
					    vidx = _.findIndex cleanArgs, (v) -> v[0] != '-'
 | 
				
			||||||
 | 
					    if vidx != -1
 | 
				
			||||||
 | 
					      oVerb = cleanArgs[ vidx ]
 | 
				
			||||||
 | 
					      verb = args[ vidx + 2 ] = oVerb.trim().toLowerCase()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Remove --options --opts -o and process separately
 | 
				
			||||||
 | 
					    optsIdx = _.findIndex cleanArgs, (v) ->
 | 
				
			||||||
 | 
					      v == '-o' || v == '--options' || v == '--opts'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if optsIdx != -1
 | 
				
			||||||
 | 
					      optStr = cleanArgs[ optsIdx + 1]
 | 
				
			||||||
 | 
					      args.splice( optsIdx + 2, 2 )
 | 
				
			||||||
 | 
					      if optStr && (optStr = optStr.trim())
 | 
				
			||||||
 | 
					        #var myJSON = JSON.parse(optStr);
 | 
				
			||||||
 | 
					        if( optStr[0] == '{')
 | 
				
			||||||
 | 
					          ### jshint ignore:start ###
 | 
				
			||||||
 | 
					          oJSON = eval('(' + optStr + ')') # jshint ignore:line <-- no worky
 | 
				
			||||||
 | 
					          ### jshint ignore:end ###
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          inf = safeLoadJSON( optStr )
 | 
				
			||||||
 | 
					          if( !inf.ex )
 | 
				
			||||||
 | 
					            oJSON = inf.json
 | 
				
			||||||
 | 
					          # TODO: Error handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Grab the --debug flag, --silent, --assert and --no-color flags
 | 
				
			||||||
 | 
					  isDebug = _.some args, (v) -> v == '-d' || v == '--debug'
 | 
				
			||||||
 | 
					  isSilent = _.some args, (v) -> v == '-s' || v == '--silent'
 | 
				
			||||||
 | 
					  isAssert = _.some args, (v) -> v == '-a' || v == '--assert'
 | 
				
			||||||
 | 
					  isMono = _.some args, (v) -> v == '--no-color'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    color: !isMono,
 | 
				
			||||||
 | 
					    debug: isDebug,
 | 
				
			||||||
 | 
					    silent: isSilent,
 | 
				
			||||||
 | 
					    assert: isAssert,
 | 
				
			||||||
 | 
					    orgVerb: oVerb,
 | 
				
			||||||
 | 
					    verb: verb,
 | 
				
			||||||
 | 
					    json: oJSON,
 | 
				
			||||||
 | 
					    args: args
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Invoke a HackMyResume verb. ###
 | 
				
			||||||
 | 
					execute = ( src, dst, opts, log ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the verb
 | 
				
			||||||
 | 
					  v = new HMR.verbs[ @name() ]()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Initialize command-specific options
 | 
				
			||||||
 | 
					  loadOptions.call( this, opts, this.parent.jsonArgs )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Set up error/output handling
 | 
				
			||||||
 | 
					  _opts.errHandler = v
 | 
				
			||||||
 | 
					  _out.init _opts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Hook up event notifications
 | 
				
			||||||
 | 
					  v.on 'hmr:status', -> _out.do.apply _out, arguments
 | 
				
			||||||
 | 
					  v.on 'hmr:error', ->  _err.err.apply _err, arguments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Invoke the verb using promise syntax
 | 
				
			||||||
 | 
					  prom = v.invoke.call v, src, dst, _opts, log
 | 
				
			||||||
 | 
					  prom.then executeSuccess, executeFail
 | 
				
			||||||
 | 
					  return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Success handler for verb invocations. Calls process.exit by default ###
 | 
				
			||||||
 | 
					executeSuccess = (obj) ->
 | 
				
			||||||
 | 
					  # Can't call _exitCallback here (process.exit) when PDF is running in BK
 | 
				
			||||||
 | 
					  #_exitCallback 0; return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Failure handler for verb invocations. Calls process.exit by default ###
 | 
				
			||||||
 | 
					executeFail = (err) ->
 | 
				
			||||||
 | 
					  finalErrorCode = -1
 | 
				
			||||||
 | 
					  if err
 | 
				
			||||||
 | 
					    finalErrorCode = if err.fluenterror then err.fluenterror else err
 | 
				
			||||||
 | 
					  if _opts.debug
 | 
				
			||||||
 | 
					    msgs = require('./msg').errors;
 | 
				
			||||||
 | 
					    logMsg printf M2C( msgs.exiting.msg, 'cyan' ), finalErrorCode
 | 
				
			||||||
 | 
					    logMsg err.stack if err.stack
 | 
				
			||||||
 | 
					  _exitCallback finalErrorCode
 | 
				
			||||||
 | 
					  return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					Initialize HackMyResume options.
 | 
				
			||||||
 | 
					TODO: Options loading is a little hacky, for two reasons:
 | 
				
			||||||
 | 
					  - Commander.js idiosyncracies
 | 
				
			||||||
 | 
					  - Need to accept JSON inputs from the command line.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					loadOptions = ( o, cmdO ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # o and this.opts() seem to be the same (command-specific options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Load the specified options file (if any) and apply options
 | 
				
			||||||
 | 
					  if( cmdO )
 | 
				
			||||||
 | 
					    o = EXTEND(true, o, cmdO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Merge in command-line options
 | 
				
			||||||
 | 
					  o = EXTEND( true, o, this.opts() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Kludge parent-level options until piping issue is resolved
 | 
				
			||||||
 | 
					  if this.parent.silent != undefined && this.parent.silent != null
 | 
				
			||||||
 | 
					    o.silent = this.parent.silent
 | 
				
			||||||
 | 
					  if this.parent.debug != undefined && this.parent.debug != null
 | 
				
			||||||
 | 
					    o.debug = this.parent.debug
 | 
				
			||||||
 | 
					  if this.parent.assert != undefined && this.parent.assert != null
 | 
				
			||||||
 | 
					    o.assert = this.parent.assert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if o.debug
 | 
				
			||||||
 | 
					    logMsg(chalk.cyan('OPTIONS:') + '\n')
 | 
				
			||||||
 | 
					    _.each(o, (val, key) ->
 | 
				
			||||||
 | 
					      logMsg(chalk.cyan('  %s') + chalk.cyan.bold(' %s'),
 | 
				
			||||||
 | 
					        PAD(key,22,null,PAD.RIGHT), val)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    logMsg('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Cache
 | 
				
			||||||
 | 
					  EXTEND( true, _opts, o )
 | 
				
			||||||
 | 
					  return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Split multiple command-line filenames by the 'TO' keyword ###
 | 
				
			||||||
 | 
					splitSrcDest = () ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  params = this.parent.args.filter((j) -> return String.is(j) )
 | 
				
			||||||
 | 
					  if params.length == 0
 | 
				
			||||||
 | 
					    throw { fluenterror: HMSTATUS.resumeNotFound, quit: true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Find the TO keyword, if any
 | 
				
			||||||
 | 
					  splitAt = _.findIndex( params, (p) -> return p.toLowerCase() == 'to'; )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # TO can't be the last keyword
 | 
				
			||||||
 | 
					  if splitAt == params.length - 1 && splitAt != -1
 | 
				
			||||||
 | 
					    logMsg(chalk.yellow('Please ') +
 | 
				
			||||||
 | 
					      chalk.yellow.bold('specify an output file') +
 | 
				
			||||||
 | 
					      chalk.yellow(' for this operation or ') +
 | 
				
			||||||
 | 
					      chalk.yellow.bold('omit the TO keyword') +
 | 
				
			||||||
 | 
					      chalk.yellow('.') )
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    src: params.slice(0, if splitAt == -1 then undefined else splitAt ),
 | 
				
			||||||
 | 
					    dst: if splitAt == -1 then [] else params.slice( splitAt + 1 )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Simple logging placeholder. ###
 | 
				
			||||||
 | 
					logMsg = () ->
 | 
				
			||||||
 | 
					  _opts.silent || console.log.apply( console.log, arguments )
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/cli/msg.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/cli/msg.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Message-handling routines for HackMyResume.
 | 
				
			||||||
 | 
					@module cli/msg
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PATH = require 'path'
 | 
				
			||||||
 | 
					YAML = require 'yamljs'
 | 
				
			||||||
 | 
					module.exports = YAML.load PATH.join __dirname, 'msg.yml'
 | 
				
			||||||
							
								
								
									
										111
									
								
								src/cli/msg.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/cli/msg.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					events:
 | 
				
			||||||
 | 
					  begin:
 | 
				
			||||||
 | 
					    msg: Invoking **%s** command.
 | 
				
			||||||
 | 
					  beforeCreate:
 | 
				
			||||||
 | 
					    msg: Creating new **%s** resume: **%s**
 | 
				
			||||||
 | 
					  afterCreate:
 | 
				
			||||||
 | 
					    msg: Creating new **%s** resume: **%s**
 | 
				
			||||||
 | 
					  afterRead:
 | 
				
			||||||
 | 
					    msg: Reading **%s** resume: **%s**
 | 
				
			||||||
 | 
					  beforeTheme:
 | 
				
			||||||
 | 
					    msg: Verifying **%s** theme.
 | 
				
			||||||
 | 
					  afterTheme:
 | 
				
			||||||
 | 
					    msg: Verifying outputs:  ???
 | 
				
			||||||
 | 
					  beforeMerge:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - "Merging **%s**"
 | 
				
			||||||
 | 
					      - " onto **%s**"
 | 
				
			||||||
 | 
					  applyTheme:
 | 
				
			||||||
 | 
					    msg: Applying **%s** theme (**%s** format%s)
 | 
				
			||||||
 | 
					  afterBuild:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - "The **%s** theme says:"
 | 
				
			||||||
 | 
					      - |
 | 
				
			||||||
 | 
					          "For best results view JSON Resume themes over a
 | 
				
			||||||
 | 
					          local or remote HTTP connection. For example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            npm install http-server -g
 | 
				
			||||||
 | 
					            http-server <resume-folder>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          For more information, see the README."
 | 
				
			||||||
 | 
					  afterGenerate:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - " (with %s)"
 | 
				
			||||||
 | 
					      - "Skipping %s resume: %s"
 | 
				
			||||||
 | 
					      - "Generating **%s** resume: **%s**"
 | 
				
			||||||
 | 
					  beforeAnalyze:
 | 
				
			||||||
 | 
					    msg: "Analyzing **%s** resume: **%s**"
 | 
				
			||||||
 | 
					  beforeConvert:
 | 
				
			||||||
 | 
					    msg: "Converting **%s** (**%s**) to **%s** (**%s**)"
 | 
				
			||||||
 | 
					  afterValidate:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - "Validating **%s** against the **%s** schema: "
 | 
				
			||||||
 | 
					      - "VALID!"
 | 
				
			||||||
 | 
					      - "INVALID"
 | 
				
			||||||
 | 
					      - "BROKEN"
 | 
				
			||||||
 | 
					      - "MISSING"
 | 
				
			||||||
 | 
					      - "ERROR"
 | 
				
			||||||
 | 
					  beforePeek:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - Peeking at **%s** in **%s**
 | 
				
			||||||
 | 
					      - Peeking at **%s**
 | 
				
			||||||
 | 
					  afterPeek:
 | 
				
			||||||
 | 
					    msg: "The specified key **%s** was not found in **%s**."
 | 
				
			||||||
 | 
					  afterInlineConvert:
 | 
				
			||||||
 | 
					    msg: Converting **%s** to **%s** format.
 | 
				
			||||||
 | 
					errors:
 | 
				
			||||||
 | 
					  themeNotFound:
 | 
				
			||||||
 | 
					    msg: >
 | 
				
			||||||
 | 
					      **Couldn't find the '%s' theme.** Please specify the name of a preinstalled
 | 
				
			||||||
 | 
					      FRESH theme or the path to a locally installed FRESH or JSON Resume theme.
 | 
				
			||||||
 | 
					  copyCSS:
 | 
				
			||||||
 | 
					    msg: Couldn't copy CSS file to destination folder.
 | 
				
			||||||
 | 
					  resumeNotFound:
 | 
				
			||||||
 | 
					    msg: Please **feed me a resume** in FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					  missingCommand:
 | 
				
			||||||
 | 
					    msg: Please **give me a command**
 | 
				
			||||||
 | 
					  invalidCommand:
 | 
				
			||||||
 | 
					    msg: Invalid command: '%s'
 | 
				
			||||||
 | 
					  resumeNotFoundAlt:
 | 
				
			||||||
 | 
					    msg: Please **feed me a resume** in either FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					  inputOutputParity:
 | 
				
			||||||
 | 
					    msg: Please **specify an output file name** for every input file you wish to convert.
 | 
				
			||||||
 | 
					  createNameMissing:
 | 
				
			||||||
 | 
					    msg: Please **specify the filename** of the resume to create.
 | 
				
			||||||
 | 
					  pdfGeneration:
 | 
				
			||||||
 | 
					    msg: PDF generation failed. Make sure wkhtmltopdf is installed and accessible from your path.
 | 
				
			||||||
 | 
					  invalid:
 | 
				
			||||||
 | 
					    msg: Validation failed and the --assert option was specified.
 | 
				
			||||||
 | 
					  invalidFormat:
 | 
				
			||||||
 | 
					    msg: The **%s** theme doesn't support the **%s** format.
 | 
				
			||||||
 | 
					  notOnPath:
 | 
				
			||||||
 | 
					    msg: %s wasn't found on your system path or is inaccessible. PDF not generated.
 | 
				
			||||||
 | 
					  readError:
 | 
				
			||||||
 | 
					    msg: Reading **???** resume: **%s**
 | 
				
			||||||
 | 
					  parseError:
 | 
				
			||||||
 | 
					    msg:
 | 
				
			||||||
 | 
					      - Invalid or corrupt JSON on line %s column %s.
 | 
				
			||||||
 | 
					      - Invalid or corrupt JSON on line %s.
 | 
				
			||||||
 | 
					      - Invalid or corrupt JSON.
 | 
				
			||||||
 | 
					  invalidHelperUse:
 | 
				
			||||||
 | 
					    msg: "**Warning**: Incorrect use of the **%s** theme helper."
 | 
				
			||||||
 | 
					  fileSaveError:
 | 
				
			||||||
 | 
					    msg: An error occurred while writing %s to disk: %s.
 | 
				
			||||||
 | 
					  mixedMerge:
 | 
				
			||||||
 | 
					    msg: "**Warning:** merging mixed resume types. Errors may occur."
 | 
				
			||||||
 | 
					  invokeTemplate:
 | 
				
			||||||
 | 
					    msg: "An error occurred during template invocation."
 | 
				
			||||||
 | 
					  compileTemplate:
 | 
				
			||||||
 | 
					    msg: "An error occurred during template compilation."
 | 
				
			||||||
 | 
					  themeLoad:
 | 
				
			||||||
 | 
					    msg: "Applying **%s** theme (? formats)"
 | 
				
			||||||
 | 
					  invalidParamCount:
 | 
				
			||||||
 | 
					    msg: "Invalid number of parameters. Expected: **%s**."
 | 
				
			||||||
 | 
					  missingParam:
 | 
				
			||||||
 | 
					    msg: The '**%s**' parameter was needed but not supplied.
 | 
				
			||||||
 | 
					  createError:
 | 
				
			||||||
 | 
					    msg: Failed to create **'%s'**.
 | 
				
			||||||
 | 
					  exiting:
 | 
				
			||||||
 | 
					    msg: Exiting with status code **%s**.
 | 
				
			||||||
 | 
					  validateError:
 | 
				
			||||||
 | 
					    msg: "An error occurred during validation:\n%s"
 | 
				
			||||||
							
								
								
									
										182
									
								
								src/cli/out.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/cli/out.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Output routines for HackMyResume.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module cli/out
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chalk = require('chalk')
 | 
				
			||||||
 | 
					HME = require('../core/event-codes')
 | 
				
			||||||
 | 
					_ = require('underscore')
 | 
				
			||||||
 | 
					M2C = require('../utils/md2chalk.js')
 | 
				
			||||||
 | 
					PATH = require('path')
 | 
				
			||||||
 | 
					LO = require('lodash')
 | 
				
			||||||
 | 
					FS = require('fs')
 | 
				
			||||||
 | 
					EXTEND = require('extend')
 | 
				
			||||||
 | 
					HANDLEBARS = require('handlebars')
 | 
				
			||||||
 | 
					YAML = require('yamljs')
 | 
				
			||||||
 | 
					printf = require('printf')
 | 
				
			||||||
 | 
					pad = require('string-padding')
 | 
				
			||||||
 | 
					dbgStyle = 'cyan';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###* A stateful output module. All HMR console output handled here. ###
 | 
				
			||||||
 | 
					module.exports = class OutputHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor: ( opts ) ->
 | 
				
			||||||
 | 
					    @init opts
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init: (opts) ->
 | 
				
			||||||
 | 
					    @opts = EXTEND( true, @opts || { }, opts )
 | 
				
			||||||
 | 
					    @msgs = YAML.load(PATH.join( __dirname, 'msg.yml' )).events
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  log: ( msg ) ->
 | 
				
			||||||
 | 
					    msg = msg || ''
 | 
				
			||||||
 | 
					    printf = require('printf')
 | 
				
			||||||
 | 
					    finished = printf.apply( printf, arguments )
 | 
				
			||||||
 | 
					    @opts.silent || console.log( finished )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  do: ( evt ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    that = @
 | 
				
			||||||
 | 
					    L = () -> that.log.apply( that, arguments )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch evt.sub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.begin
 | 
				
			||||||
 | 
					        this.opts.debug &&
 | 
				
			||||||
 | 
					        L( M2C( this.msgs.begin.msg, dbgStyle), evt.cmd.toUpperCase() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #when HME.beforeCreate
 | 
				
			||||||
 | 
					        #L( M2C( this.msgs.beforeCreate.msg, 'green' ), evt.fmt, evt.file )
 | 
				
			||||||
 | 
					        #break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterCreate
 | 
				
			||||||
 | 
					        L( M2C( @msgs.beforeCreate.msg, if evt.isError then 'red' else 'green' ), evt.fmt, evt.file )
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.beforeTheme
 | 
				
			||||||
 | 
					        this.opts.debug &&
 | 
				
			||||||
 | 
					          L( M2C( this.msgs.beforeTheme.msg, dbgStyle), evt.theme.toUpperCase() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterParse
 | 
				
			||||||
 | 
					        L( M2C( this.msgs.afterRead.msg, 'gray', 'white.dim'), evt.fmt.toUpperCase(), evt.file )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.beforeMerge
 | 
				
			||||||
 | 
					        msg = ''
 | 
				
			||||||
 | 
					        evt.f.reverse().forEach ( a, idx ) ->
 | 
				
			||||||
 | 
					          msg += printf( (if idx == 0 then @msgs.beforeMerge.msg[0] else @msgs.beforeMerge.msg[1]), a.file )
 | 
				
			||||||
 | 
					        , @
 | 
				
			||||||
 | 
					        L( M2C(msg, (if evt.mixed then 'yellow' else 'gray'), 'white.dim') )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.applyTheme
 | 
				
			||||||
 | 
					        @theme = evt.theme;
 | 
				
			||||||
 | 
					        numFormats = Object.keys( evt.theme.formats ).length;
 | 
				
			||||||
 | 
					        L( M2C(this.msgs.applyTheme.msg,
 | 
				
			||||||
 | 
					          if evt.status == 'error' then 'red' else 'gray',
 | 
				
			||||||
 | 
					          if evt.status == 'error' then 'bold' else 'white.dim'),
 | 
				
			||||||
 | 
					          evt.theme.name.toUpperCase(),
 | 
				
			||||||
 | 
					          numFormats, if numFormats == 1 then '' else 's' )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.end
 | 
				
			||||||
 | 
					        if evt.cmd == 'build'
 | 
				
			||||||
 | 
					          themeName = this.theme.name.toUpperCase()
 | 
				
			||||||
 | 
					          if this.opts.tips && (this.theme.message || this.theme.render)
 | 
				
			||||||
 | 
					            WRAP = require('word-wrap')
 | 
				
			||||||
 | 
					            if this.theme.message
 | 
				
			||||||
 | 
					              L( M2C( this.msgs.afterBuild.msg[0], 'cyan' ), themeName )
 | 
				
			||||||
 | 
					              L( M2C( this.theme.message, 'white' ))
 | 
				
			||||||
 | 
					            else if this.theme.render
 | 
				
			||||||
 | 
					              L( M2C( this.msgs.afterBuild.msg[0], 'cyan'), themeName)
 | 
				
			||||||
 | 
					              L( M2C( this.msgs.afterBuild.msg[1], 'white'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterGenerate
 | 
				
			||||||
 | 
					        suffix = ''
 | 
				
			||||||
 | 
					        if evt.fmt == 'pdf'
 | 
				
			||||||
 | 
					          if this.opts.pdf
 | 
				
			||||||
 | 
					            if this.opts.pdf != 'none'
 | 
				
			||||||
 | 
					              suffix = printf( M2C( this.msgs.afterGenerate.msg[0], if evt.error then 'red' else 'green' ), this.opts.pdf )
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              L( M2C( this.msgs.afterGenerate.msg[1], 'gray' ), evt.fmt.toUpperCase(), evt.file )
 | 
				
			||||||
 | 
					              return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        L( M2C( this.msgs.afterGenerate.msg[2] + suffix, if evt.error then 'red' else 'green' ),
 | 
				
			||||||
 | 
					            pad( evt.fmt.toUpperCase(),4,null,pad.RIGHT ),
 | 
				
			||||||
 | 
					            PATH.relative( process.cwd(), evt.file ) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.beforeAnalyze
 | 
				
			||||||
 | 
					        L( M2C( this.msgs.beforeAnalyze.msg, 'green' ), evt.fmt, evt.file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterAnalyze
 | 
				
			||||||
 | 
					        info = evt.info
 | 
				
			||||||
 | 
					        rawTpl = FS.readFileSync( PATH.join( __dirname, 'analyze.hbs' ), 'utf8')
 | 
				
			||||||
 | 
					        HANDLEBARS.registerHelper( require('../helpers/console-helpers') )
 | 
				
			||||||
 | 
					        template = HANDLEBARS.compile(rawTpl, { strict: false, assumeObjects: false })
 | 
				
			||||||
 | 
					        tot = 0
 | 
				
			||||||
 | 
					        info.keywords.forEach (g) -> tot += g.count
 | 
				
			||||||
 | 
					        info.keywords.totalKeywords = tot
 | 
				
			||||||
 | 
					        output = template( info )
 | 
				
			||||||
 | 
					        @log( chalk.cyan(output) )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.beforeConvert
 | 
				
			||||||
 | 
					        L( M2C( this.msgs.beforeConvert.msg, 'green' ),
 | 
				
			||||||
 | 
					          evt.srcFile, evt.srcFmt, evt.dstFile, evt.dstFmt
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterInlineConvert
 | 
				
			||||||
 | 
					        L( M2C( this.msgs.afterInlineConvert.msg, 'gray', 'white.dim' ),
 | 
				
			||||||
 | 
					          evt.file, evt.fmt );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterValidate
 | 
				
			||||||
 | 
					        style = 'red'
 | 
				
			||||||
 | 
					        adj = ''
 | 
				
			||||||
 | 
					        msgs = @msgs.afterValidate.msg;
 | 
				
			||||||
 | 
					        switch evt.status
 | 
				
			||||||
 | 
					          when 'valid' then style = 'green'; adj = msgs[1]
 | 
				
			||||||
 | 
					          when 'invalid' then style = 'yellow'; adj = msgs[2]
 | 
				
			||||||
 | 
					          when 'broken' then style = 'red'; adj = msgs[3]
 | 
				
			||||||
 | 
					          when 'missing' then style = 'red'; adj = msgs[4]
 | 
				
			||||||
 | 
					          when 'unknown' then style = 'red'; adj = msgs[5]
 | 
				
			||||||
 | 
					        evt.schema = evt.schema.replace('jars','JSON Resume').toUpperCase()
 | 
				
			||||||
 | 
					        L(M2C( msgs[0], 'white' ) + chalk[style].bold(adj), evt.file, evt.schema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if evt.violations
 | 
				
			||||||
 | 
					          _.each evt.violations, (err,idx) ->
 | 
				
			||||||
 | 
					            L( chalk.yellow.bold('--> ') +
 | 
				
			||||||
 | 
					               chalk.yellow(err.field.replace('data.','resume.').toUpperCase() +
 | 
				
			||||||
 | 
					               ' ' + err.message))
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          , @
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when HME.afterPeek
 | 
				
			||||||
 | 
					        sty = if evt.error then 'red' else ( if evt.target != undefined then 'green' else 'yellow' )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # "Peeking at 'someKey' in 'someFile'."
 | 
				
			||||||
 | 
					        if evt.requested
 | 
				
			||||||
 | 
					          L(M2C(this.msgs.beforePeek.msg[0], sty), evt.requested, evt.file)
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          L(M2C(this.msgs.beforePeek.msg[1], sty), evt.file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If the key was present, print it
 | 
				
			||||||
 | 
					        if evt.target != undefined and !evt.error
 | 
				
			||||||
 | 
					          console.dir( evt.target, { depth: null, colors: true } )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If the key was not present, but no error occurred, print it
 | 
				
			||||||
 | 
					        else if !evt.error
 | 
				
			||||||
 | 
					          L M2C( this.msgs.afterPeek.msg, 'yellow'), evt.requested, evt.file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else if evt.error
 | 
				
			||||||
 | 
					          L chalk.red( evt.error.inner.inner )
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/cli/use.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/cli/use.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					Usage:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hackmyresume <command> <sources> [TO <targets>] [<options>]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Available commands:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BUILD         Build your resume to the destination format(s).
 | 
				
			||||||
 | 
					  ANALYZE       Analyze your resume for keywords, gaps, and metrics.
 | 
				
			||||||
 | 
					  VALIDATE      Validate your resume for errors and typos.
 | 
				
			||||||
 | 
					  CONVERT       Convert your resume between FRESH and JSON Resume.
 | 
				
			||||||
 | 
					  NEW           Create a new resume in FRESH or JSON Resume format.
 | 
				
			||||||
 | 
					  PEEK          View a specific field or element on your resume.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Available options:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --theme -t    Path to a FRESH or JSON Resume theme.
 | 
				
			||||||
 | 
					  --pdf -p      Specify the PDF engine to use (wkhtmltopdf or phantom).
 | 
				
			||||||
 | 
					  --options -o  Load options from an external JSON file.
 | 
				
			||||||
 | 
					  --format -f   The format (FRESH or JSON Resume) to use.
 | 
				
			||||||
 | 
					  --debug -d    Emit extended debugging info.
 | 
				
			||||||
 | 
					  --assert -a   Treat resume validation warnings as errors.
 | 
				
			||||||
 | 
					  --no-colors   Disable terminal colors.
 | 
				
			||||||
 | 
					  --tips        Display theme messages and tips.
 | 
				
			||||||
 | 
					  --help -h     Display help documentation.
 | 
				
			||||||
 | 
					  --version -v  Display the current version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Not all options are supported for all commands. For example, the
 | 
				
			||||||
 | 
					--theme option is only supported for the BUILD command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Examples:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hackmyresume  BUILD resume.json TO out/resume.all --theme modern
 | 
				
			||||||
 | 
					  hackmyresume  ANALYZE resume.json
 | 
				
			||||||
 | 
					  hackmyresume  NEW my-new-resume.json --format JRS
 | 
				
			||||||
 | 
					  hackmyresume  CONVERT resume-fresh.json TO resume-jrs.json
 | 
				
			||||||
 | 
					  hackmyresume  VALIDATE resume.json
 | 
				
			||||||
 | 
					  hackmyresume  PEEK resume.json employment[2].summary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tips:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - You can specify multiple sources and/or targets for all commands.
 | 
				
			||||||
 | 
					  - You can use any FRESH or JSON Resume theme with HackMyResume.
 | 
				
			||||||
 | 
					  - Specify a file extension of .all to generate your resume to all
 | 
				
			||||||
 | 
					    available formats supported by the theme. (BUILD command.)
 | 
				
			||||||
 | 
					  - The --theme parameter can specify either the name of a preinstalled
 | 
				
			||||||
 | 
					    theme, or the path to a local FRESH or JSON Resume theme.
 | 
				
			||||||
 | 
					  - Visit https://www.npmjs.com/search?q=jsonresume-theme for a full
 | 
				
			||||||
 | 
					    listing of all available JSON Resume themes.
 | 
				
			||||||
 | 
					  - Visit https://github.com/fluentdesk/fresh-themes for a complete
 | 
				
			||||||
 | 
					    listing of all available FRESH themes.
 | 
				
			||||||
 | 
					  - Report bugs to https://githut.com/hacksalot/HackMyResume/issues.
 | 
				
			||||||
							
								
								
									
										53
									
								
								src/core/abstract-resume.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/core/abstract-resume.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the AbstractResume class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/abstract-resume
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_ = require 'underscore'
 | 
				
			||||||
 | 
					__ = require 'lodash'
 | 
				
			||||||
 | 
					FluentDate = require('./fluent-date')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AbstractResume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Compute the total duration of the work history.
 | 
				
			||||||
 | 
					  @returns The total duration of the sheet's work history, that is, the number
 | 
				
			||||||
 | 
					  of years between the start date of the earliest job on the resume and the
 | 
				
			||||||
 | 
					  *latest end date of all jobs in the work history*. This last condition is for
 | 
				
			||||||
 | 
					  sheets that have overlapping jobs.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  duration: (collKey, startKey, endKey, unit) ->
 | 
				
			||||||
 | 
					    unit = unit || 'years'
 | 
				
			||||||
 | 
					    hist = __.get @, collKey
 | 
				
			||||||
 | 
					    return 0 if !hist or !hist.length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # BEGIN CODE DUPLICATION --> src/inspectors/gap-inspector.coffee (TODO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Convert the candidate's employment history to an array of dates,
 | 
				
			||||||
 | 
					    # where each element in the array is a start date or an end date of a
 | 
				
			||||||
 | 
					    # job -- it doesn't matter which.
 | 
				
			||||||
 | 
					    new_e = hist.map ( job ) ->
 | 
				
			||||||
 | 
					      obj = _.pick( job, [startKey, endKey] )
 | 
				
			||||||
 | 
					      # Synthesize an end date if this is a "current" gig
 | 
				
			||||||
 | 
					      obj[endKey] = 'current' if !_.has obj, endKey
 | 
				
			||||||
 | 
					      if obj && (obj[startKey] || obj[endKey])
 | 
				
			||||||
 | 
					        obj = _.pairs obj
 | 
				
			||||||
 | 
					        obj[0][1] = FluentDate.fmt( obj[0][1] )
 | 
				
			||||||
 | 
					        if obj.length > 1
 | 
				
			||||||
 | 
					          obj[1][1] = FluentDate.fmt( obj[1][1] )
 | 
				
			||||||
 | 
					      obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Flatten the array, remove empties, and sort
 | 
				
			||||||
 | 
					    new_e = _.filter _.flatten( new_e, true ), (v) ->
 | 
				
			||||||
 | 
					      return v && v.length && v[0] && v[0].length
 | 
				
			||||||
 | 
					    return 0 if !new_e or !new_e.length
 | 
				
			||||||
 | 
					    new_e = _.sortBy new_e, ( elem ) -> return elem[1].unix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # END CODE DUPLICATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    firstDate = _.first( new_e )[1];
 | 
				
			||||||
 | 
					    lastDate = _.last( new_e )[1];
 | 
				
			||||||
 | 
					    lastDate.diff firstDate, unit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = AbstractResume
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/core/default-formats.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/core/default-formats.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					###
 | 
				
			||||||
 | 
					Event code definitions.
 | 
				
			||||||
 | 
					@module core/default-formats
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###* Supported resume formats. ###
 | 
				
			||||||
 | 
					module.exports = [
 | 
				
			||||||
 | 
					  { name: 'html', ext: 'html', gen: new (require('../generators/html-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'txt',  ext: 'txt', gen: new (require('../generators/text-generator'))()  },
 | 
				
			||||||
 | 
					  { name: 'doc',  ext: 'doc',  fmt: 'xml', gen: new (require('../generators/word-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'pdf',  ext: 'pdf', fmt: 'html', is: false, gen: new (require('../generators/html-pdf-cli-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'png',  ext: 'png', fmt: 'html', is: false, gen: new (require('../generators/html-png-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'md', ext: 'md', fmt: 'txt', gen: new (require('../generators/markdown-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'json', ext: 'json', gen: new (require('../generators/json-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'yml', ext: 'yml', fmt: 'yml', gen: new (require('../generators/json-yaml-generator'))() },
 | 
				
			||||||
 | 
					  { name: 'latex', ext: 'tex', fmt: 'latex', gen: new (require('../generators/latex-generator'))() }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/core/default-options.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/core/default-options.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					###
 | 
				
			||||||
 | 
					Event code definitions.
 | 
				
			||||||
 | 
					@module core/default-options
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports =
 | 
				
			||||||
 | 
					  theme: 'modern'
 | 
				
			||||||
 | 
					  prettify: # ← See https://github.com/beautify-web/js-beautify#options
 | 
				
			||||||
 | 
					    indent_size: 2
 | 
				
			||||||
 | 
					    unformatted: ['em','strong']
 | 
				
			||||||
 | 
					    max_char: 80, # ← See lib/html.js in above-linked repo
 | 
				
			||||||
 | 
					    # wrap_line_length: 120, ← Don't use this
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/core/empty-jrs.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/core/empty-jrs.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "basics": {
 | 
				
			||||||
 | 
					    "name": "",
 | 
				
			||||||
 | 
					    "label": "",
 | 
				
			||||||
 | 
					    "picture": "",
 | 
				
			||||||
 | 
					    "email": "",
 | 
				
			||||||
 | 
					    "phone": "",
 | 
				
			||||||
 | 
					    "degree": "",
 | 
				
			||||||
 | 
					    "website": "",
 | 
				
			||||||
 | 
					    "summary": "",
 | 
				
			||||||
 | 
					    "location": {
 | 
				
			||||||
 | 
					      "address": "",
 | 
				
			||||||
 | 
					      "postalCode": "",
 | 
				
			||||||
 | 
					      "city": "",
 | 
				
			||||||
 | 
					      "countryCode": "",
 | 
				
			||||||
 | 
					      "region": ""
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "profiles": [{
 | 
				
			||||||
 | 
					      "network": "",
 | 
				
			||||||
 | 
					      "username": "",
 | 
				
			||||||
 | 
					      "url": ""
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "work": [{
 | 
				
			||||||
 | 
					    "company": "",
 | 
				
			||||||
 | 
					    "position": "",
 | 
				
			||||||
 | 
					    "website": "",
 | 
				
			||||||
 | 
					    "startDate": "",
 | 
				
			||||||
 | 
					    "endDate": "",
 | 
				
			||||||
 | 
					    "summary": "",
 | 
				
			||||||
 | 
					    "highlights": [
 | 
				
			||||||
 | 
					      ""
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "awards": [{
 | 
				
			||||||
 | 
					    "title": "",
 | 
				
			||||||
 | 
					    "date": "",
 | 
				
			||||||
 | 
					    "awarder": "",
 | 
				
			||||||
 | 
					    "summary": ""
 | 
				
			||||||
 | 
					  }],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "education": [{
 | 
				
			||||||
 | 
					    "institution": "",
 | 
				
			||||||
 | 
					    "area": "",
 | 
				
			||||||
 | 
					    "studyType": "",
 | 
				
			||||||
 | 
					    "startDate": "",
 | 
				
			||||||
 | 
					    "endDate": "",
 | 
				
			||||||
 | 
					    "gpa": "",
 | 
				
			||||||
 | 
					    "courses": [ "" ]
 | 
				
			||||||
 | 
					  }],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "publications": [{
 | 
				
			||||||
 | 
					    "name": "",
 | 
				
			||||||
 | 
					    "publisher": "",
 | 
				
			||||||
 | 
					    "releaseDate": "",
 | 
				
			||||||
 | 
					    "website": "",
 | 
				
			||||||
 | 
					    "summary": ""
 | 
				
			||||||
 | 
					  }],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "volunteer": [{
 | 
				
			||||||
 | 
					    "organization": "",
 | 
				
			||||||
 | 
					    "position": "",
 | 
				
			||||||
 | 
					    "website": "",
 | 
				
			||||||
 | 
					    "startDate": "",
 | 
				
			||||||
 | 
					    "endDate": "",
 | 
				
			||||||
 | 
					    "summary": "",
 | 
				
			||||||
 | 
					    "highlights": [ "" ]
 | 
				
			||||||
 | 
					  }],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "skills": [{
 | 
				
			||||||
 | 
					      "name": "",
 | 
				
			||||||
 | 
					      "level": "",
 | 
				
			||||||
 | 
					      "keywords": [""]
 | 
				
			||||||
 | 
					  }]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/core/event-codes.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/event-codes.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					###
 | 
				
			||||||
 | 
					Event code definitions.
 | 
				
			||||||
 | 
					@module core/event-codes
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports =
 | 
				
			||||||
 | 
					  error:            -1
 | 
				
			||||||
 | 
					  success:          0
 | 
				
			||||||
 | 
					  begin:            1
 | 
				
			||||||
 | 
					  end:              2
 | 
				
			||||||
 | 
					  beforeRead:       3
 | 
				
			||||||
 | 
					  afterRead:        4
 | 
				
			||||||
 | 
					  beforeCreate:     5
 | 
				
			||||||
 | 
					  afterCreate:      6
 | 
				
			||||||
 | 
					  beforeTheme:      7
 | 
				
			||||||
 | 
					  afterTheme:       8
 | 
				
			||||||
 | 
					  beforeMerge:      9
 | 
				
			||||||
 | 
					  afterMerge:       10
 | 
				
			||||||
 | 
					  beforeGenerate:   11
 | 
				
			||||||
 | 
					  afterGenerate:    12
 | 
				
			||||||
 | 
					  beforeAnalyze:    13
 | 
				
			||||||
 | 
					  afterAnalyze:     14
 | 
				
			||||||
 | 
					  beforeConvert:    15
 | 
				
			||||||
 | 
					  afterConvert:     16
 | 
				
			||||||
 | 
					  verifyOutputs:    17
 | 
				
			||||||
 | 
					  beforeParse:      18
 | 
				
			||||||
 | 
					  afterParse:       19
 | 
				
			||||||
 | 
					  beforePeek:       20
 | 
				
			||||||
 | 
					  afterPeek:        21
 | 
				
			||||||
 | 
					  beforeInlineConvert: 22
 | 
				
			||||||
 | 
					  afterInlineConvert: 23
 | 
				
			||||||
 | 
					  beforeValidate:   24
 | 
				
			||||||
 | 
					  afterValidate:    25
 | 
				
			||||||
 | 
					  beforeWrite:      26
 | 
				
			||||||
 | 
					  afterWrite:       27
 | 
				
			||||||
 | 
					  applyTheme:       28
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/core/fluent-date.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/core/fluent-date.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					The HackMyResume date representation.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/fluent-date
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					moment = require 'moment'
 | 
				
			||||||
 | 
					require('../utils/string')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Create a FluentDate from a string or Moment date object. There are a few date
 | 
				
			||||||
 | 
					formats to be aware of here.
 | 
				
			||||||
 | 
					1. The words "Present" and "Now", referring to the current date
 | 
				
			||||||
 | 
					2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
 | 
				
			||||||
 | 
					3. Year-and-month only ("2015-04")
 | 
				
			||||||
 | 
					4. Year-only "YYYY" ("2015")
 | 
				
			||||||
 | 
					5. The friendly HackMyResume "mmm YYYY" format ("Mar 2015" or "Dec 2008")
 | 
				
			||||||
 | 
					6. Empty dates ("", " ")
 | 
				
			||||||
 | 
					7. Any other date format that Moment.js can parse from
 | 
				
			||||||
 | 
					Note: Moment can transparently parse all or most of these, without requiring us
 | 
				
			||||||
 | 
					to specify a date format...but for maximum parsing safety and to avoid Moment
 | 
				
			||||||
 | 
					deprecation warnings, it's recommended to either a) explicitly specify the date
 | 
				
			||||||
 | 
					format or b) use an ISO format. For clarity, we handle these cases explicitly.
 | 
				
			||||||
 | 
					@class FluentDate
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FluentDate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor: (dt) ->
 | 
				
			||||||
 | 
					    @rep = this.fmt dt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @isCurrent: (dt) ->
 | 
				
			||||||
 | 
					    !dt || (String.is(dt) and /^(present|now|current)$/.test(dt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					months = {}
 | 
				
			||||||
 | 
					abbr = {}
 | 
				
			||||||
 | 
					moment.months().forEach((m,idx) -> months[m.toLowerCase()] = idx+1 )
 | 
				
			||||||
 | 
					moment.monthsShort().forEach((m,idx) -> abbr[m.toLowerCase()]=idx+1 )
 | 
				
			||||||
 | 
					abbr.sept = 9
 | 
				
			||||||
 | 
					module.exports = FluentDate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FluentDate.fmt = ( dt, throws ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throws = (throws == undefined || throws == null) || throws
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if typeof dt == 'string' or dt instanceof String
 | 
				
			||||||
 | 
					    dt = dt.toLowerCase().trim()
 | 
				
			||||||
 | 
					    if /^(present|now|current)$/.test(dt) # "Present", "Now"
 | 
				
			||||||
 | 
					      return moment()
 | 
				
			||||||
 | 
					    else if /^\D+\s+\d{4}$/.test(dt) # "Mar 2015"
 | 
				
			||||||
 | 
					      parts = dt.split(' ');
 | 
				
			||||||
 | 
					      month = (months[parts[0]] || abbr[parts[0]]);
 | 
				
			||||||
 | 
					      temp = parts[1] + '-' + (month < 10 ? '0' + month : month.toString());
 | 
				
			||||||
 | 
					      return moment temp, 'YYYY-MM'
 | 
				
			||||||
 | 
					    else if /^\d{4}-\d{1,2}$/.test(dt) # "2015-03", "1998-4"
 | 
				
			||||||
 | 
					      return moment dt, 'YYYY-MM'
 | 
				
			||||||
 | 
					    else if /^\s*\d{4}\s*$/.test(dt) # "2015"
 | 
				
			||||||
 | 
					      return moment dt, 'YYYY'
 | 
				
			||||||
 | 
					    else if /^\s*$/.test(dt) # "", " "
 | 
				
			||||||
 | 
					      return moment()
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      mt = moment dt
 | 
				
			||||||
 | 
					      if mt.isValid()
 | 
				
			||||||
 | 
					        return mt
 | 
				
			||||||
 | 
					      if throws
 | 
				
			||||||
 | 
					        throw 'Invalid date format encountered.'
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    if !dt
 | 
				
			||||||
 | 
					      return moment()
 | 
				
			||||||
 | 
					    else if dt.isValid and dt.isValid()
 | 
				
			||||||
 | 
					      return dt
 | 
				
			||||||
 | 
					    if throws
 | 
				
			||||||
 | 
					      throw 'Unknown date object encountered.'
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
@@ -1,80 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
The FluentCV date representation.
 | 
					 | 
				
			||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var moment = require('moment');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
Create a FluentDate from a string or Moment date object. There are a few date
 | 
					 | 
				
			||||||
formats to be aware of here.
 | 
					 | 
				
			||||||
1. The words "Present" and "Now", referring to the current date
 | 
					 | 
				
			||||||
2. The default "YYYY-MM-DD" format used in JSON Resume ("2015-02-10")
 | 
					 | 
				
			||||||
3. Year-and-month only ("2015-04")
 | 
					 | 
				
			||||||
4. Year-only "YYYY" ("2015")
 | 
					 | 
				
			||||||
5. The friendly FluentCV "mmm YYYY" format ("Mar 2015" or "Dec 2008")
 | 
					 | 
				
			||||||
6. Empty dates ("", " ")
 | 
					 | 
				
			||||||
7. Any other date format that Moment.js can parse from
 | 
					 | 
				
			||||||
Note: Moment can transparently parse all or most of these, without requiring us
 | 
					 | 
				
			||||||
to specify a date format...but for maximum parsing safety and to avoid Moment
 | 
					 | 
				
			||||||
deprecation warnings, it's recommended to either a) explicitly specify the date
 | 
					 | 
				
			||||||
format or b) use an ISO format. For clarity, we handle these cases explicitly.
 | 
					 | 
				
			||||||
@class FluentDate
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
function FluentDate( dt ) {
 | 
					 | 
				
			||||||
  this.rep = this.fmt( dt );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FluentDate/*.prototype*/.fmt = function( dt ) {
 | 
					 | 
				
			||||||
  if( (typeof dt === 'string' || dt instanceof String) ) {
 | 
					 | 
				
			||||||
    dt = dt.toLowerCase().trim();
 | 
					 | 
				
			||||||
    if( /^(present|now)$/.test(dt) ) { // "Present", "Now"
 | 
					 | 
				
			||||||
      return moment();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if( /^\D+\s+\d{4}$/.test(dt) ) { // "Mar 2015"
 | 
					 | 
				
			||||||
      var parts = dt.split(' ');
 | 
					 | 
				
			||||||
      var month = (months[parts[0]] || abbr[parts[0]]);
 | 
					 | 
				
			||||||
      var dt = parts[1] + '-' + (month < 10 ? '0' + month : month.toString());
 | 
					 | 
				
			||||||
      return moment( dt, 'YYYY-MM' );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if( /^\d{4}-\d{1,2}$/.test(dt) ) { // "2015-03", "1998-4"
 | 
					 | 
				
			||||||
      return moment( dt, 'YYYY-MM' );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if( /^\s\d{4}$/.test(dt) ) { // "2015"
 | 
					 | 
				
			||||||
      return moment( dt, 'YYYY' );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if( /^\s*$/.test(dt) ) { // "", " "
 | 
					 | 
				
			||||||
      var defTime = {
 | 
					 | 
				
			||||||
        isNull: true,
 | 
					 | 
				
			||||||
        isBefore: function( other ) {
 | 
					 | 
				
			||||||
          return( other && !other.isNull ) ? true : false;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        isAfter: function( other ) {
 | 
					 | 
				
			||||||
          return( other && !other.isNull ) ? false : false;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        unix: function() { return 0; },
 | 
					 | 
				
			||||||
        format: function() { return ''; },
 | 
					 | 
				
			||||||
        diff: function() { return 0; }
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      return defTime;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      var mt = moment( dt );
 | 
					 | 
				
			||||||
      if(mt.isValid())
 | 
					 | 
				
			||||||
        return mt;
 | 
					 | 
				
			||||||
      throw 'Invalid date format encountered.';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    if( dt.isValid && dt.isValid() )
 | 
					 | 
				
			||||||
      return dt;
 | 
					 | 
				
			||||||
    throw 'Unknown date object encountered.';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var months = {}, abbr = {};
 | 
					 | 
				
			||||||
moment.months().forEach(function(m,idx){months[m.toLowerCase()]=idx+1;});
 | 
					 | 
				
			||||||
moment.monthsShort().forEach(function(m,idx){abbr[m.toLowerCase()]=idx+1;});
 | 
					 | 
				
			||||||
abbr.sept = 9;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = FluentDate;
 | 
					 | 
				
			||||||
							
								
								
									
										410
									
								
								src/core/fresh-resume.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								src/core/fresh-resume.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,410 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the FRESHResume class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/fresh-resume
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FS = require 'fs'
 | 
				
			||||||
 | 
					extend = require 'extend'
 | 
				
			||||||
 | 
					validator = require 'is-my-json-valid'
 | 
				
			||||||
 | 
					_ = require 'underscore'
 | 
				
			||||||
 | 
					__ = require 'lodash'
 | 
				
			||||||
 | 
					PATH = require 'path'
 | 
				
			||||||
 | 
					moment = require 'moment'
 | 
				
			||||||
 | 
					XML = require 'xml-escape'
 | 
				
			||||||
 | 
					MD = require 'marked'
 | 
				
			||||||
 | 
					CONVERTER = require 'fresh-jrs-converter'
 | 
				
			||||||
 | 
					JRSResume = require './jrs-resume'
 | 
				
			||||||
 | 
					FluentDate = require './fluent-date'
 | 
				
			||||||
 | 
					AbstractResume = require './abstract-resume'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					A FRESH resume or CV. FRESH resumes are backed by JSON, and each FreshResume
 | 
				
			||||||
 | 
					object is an instantiation of that JSON decorated with utility methods.
 | 
				
			||||||
 | 
					@constructor
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					class FreshResume extends AbstractResume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Initialize the the FreshResume from JSON string data. ###
 | 
				
			||||||
 | 
					  parse: ( stringData, opts ) ->
 | 
				
			||||||
 | 
					    @imp = @imp ? raw: stringData
 | 
				
			||||||
 | 
					    this.parseJSON JSON.parse( stringData ), opts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Initialize the FreshResume from JSON.
 | 
				
			||||||
 | 
					  Open and parse the specified FRESH resume. Merge the JSON object model onto
 | 
				
			||||||
 | 
					  this Sheet instance with extend() and convert sheet dates to a safe &
 | 
				
			||||||
 | 
					  consistent format. Then sort each section by startDate descending.
 | 
				
			||||||
 | 
					  @param rep {Object} The raw JSON representation.
 | 
				
			||||||
 | 
					  @param opts {Object} Resume loading and parsing options.
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    date: Perform safe date conversion.
 | 
				
			||||||
 | 
					    sort: Sort resume items by date.
 | 
				
			||||||
 | 
					    compute: Prepare computed resume totals.
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  parseJSON: ( rep, opts ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ignore any element with the 'ignore: true' designator.
 | 
				
			||||||
 | 
					    that = @
 | 
				
			||||||
 | 
					    traverse = require 'traverse'
 | 
				
			||||||
 | 
					    ignoreList = []
 | 
				
			||||||
 | 
					    scrubbed = traverse( rep ).map ( x ) ->
 | 
				
			||||||
 | 
					      if !@isLeaf && @node.ignore
 | 
				
			||||||
 | 
					        if @node.ignore == true || this.node.ignore == 'true'
 | 
				
			||||||
 | 
					          ignoreList.push this.node
 | 
				
			||||||
 | 
					          @remove()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Now apply the resume representation onto this object
 | 
				
			||||||
 | 
					    extend( true, @, scrubbed );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If the resume has already been processed, then we are being called from
 | 
				
			||||||
 | 
					    # the .dupe method, and there's no need to do any post processing
 | 
				
			||||||
 | 
					    if !@imp?.processed
 | 
				
			||||||
 | 
					      # Set up metadata TODO: Clean up metadata on the object model.
 | 
				
			||||||
 | 
					      opts = opts || { }
 | 
				
			||||||
 | 
					      if opts.imp == undefined || opts.imp
 | 
				
			||||||
 | 
					        @imp = @imp || { }
 | 
				
			||||||
 | 
					        @imp.title = (opts.title || @imp.title) || @name
 | 
				
			||||||
 | 
					        unless @imp.raw
 | 
				
			||||||
 | 
					          @imp.raw = JSON.stringify rep
 | 
				
			||||||
 | 
					      @imp.processed = true
 | 
				
			||||||
 | 
					      # Parse dates, sort dates, and calculate computed values
 | 
				
			||||||
 | 
					      (opts.date == undefined || opts.date) && _parseDates.call( this );
 | 
				
			||||||
 | 
					      (opts.sort == undefined || opts.sort) && this.sort();
 | 
				
			||||||
 | 
					      (opts.compute == undefined || opts.compute) && (@computed = {
 | 
				
			||||||
 | 
					         numYears: this.duration(),
 | 
				
			||||||
 | 
					         keywords: this.keywords()
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Save the sheet to disk (for environments that have disk access). ###
 | 
				
			||||||
 | 
					  save: ( filename ) ->
 | 
				
			||||||
 | 
					    @imp.file = filename || @imp.file
 | 
				
			||||||
 | 
					    FS.writeFileSync @imp.file, @stringify(), 'utf8'
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Save the sheet to disk in a specific format, either FRESH or JSON Resume.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  saveAs: ( filename, format ) ->
 | 
				
			||||||
 | 
					    if format != 'JRS'
 | 
				
			||||||
 | 
					      @imp.file = filename || @imp.file
 | 
				
			||||||
 | 
					      FS.writeFileSync @imp.file, @stringify(), 'utf8'
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      newRep = CONVERTER.toJRS this
 | 
				
			||||||
 | 
					      FS.writeFileSync filename, JRSResume.stringify( newRep ), 'utf8'
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Duplicate this FreshResume instance.
 | 
				
			||||||
 | 
					  This method first extend()s this object onto an empty, creating a deep copy,
 | 
				
			||||||
 | 
					  and then passes the result into a new FreshResume instance via .parseJSON.
 | 
				
			||||||
 | 
					  We do it this way to create a true clone of the object without re-running any
 | 
				
			||||||
 | 
					  of the associated processing.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  dupe: () ->
 | 
				
			||||||
 | 
					    jso = extend true, { }, @
 | 
				
			||||||
 | 
					    rnew = new FreshResume()
 | 
				
			||||||
 | 
					    rnew.parseJSON jso, { }
 | 
				
			||||||
 | 
					    rnew
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Convert this object to a JSON string, sanitizing meta-properties along the
 | 
				
			||||||
 | 
					  way.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  stringify: () -> FreshResume.stringify @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Create a copy of this resume in which all string fields have been run through
 | 
				
			||||||
 | 
					  a transformation function (such as a Markdown filter or XML encoder).
 | 
				
			||||||
 | 
					  TODO: Move this out of FRESHResume.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  transformStrings: ( filt, transformer ) ->
 | 
				
			||||||
 | 
					    ret = this.dupe()
 | 
				
			||||||
 | 
					    trx = require '../utils/string-transformer'
 | 
				
			||||||
 | 
					    trx ret, filt, transformer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Create a copy of this resume in which all fields have been interpreted as
 | 
				
			||||||
 | 
					  Markdown.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  markdownify: () ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MDIN = ( txt ) ->
 | 
				
			||||||
 | 
					      return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    trx = ( key, val ) ->
 | 
				
			||||||
 | 
					      if key == 'summary'
 | 
				
			||||||
 | 
					        return MD val
 | 
				
			||||||
 | 
					      MDIN(val)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return @transformStrings ['skills','url','start','end','date'], trx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Create a copy of this resume in which all fields have been interpreted as
 | 
				
			||||||
 | 
					  Markdown.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  xmlify: () ->
 | 
				
			||||||
 | 
					    trx = (key, val) -> XML val
 | 
				
			||||||
 | 
					    return @transformStrings [], trx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Return the resume format. ###
 | 
				
			||||||
 | 
					  format: () -> 'FRESH'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Return internal metadata. Create if it doesn't exist.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  i: () -> this.imp = this.imp || { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Return a unique list of all keywords across all skills. ###
 | 
				
			||||||
 | 
					  keywords: () ->
 | 
				
			||||||
 | 
					    flatSkills = []
 | 
				
			||||||
 | 
					    if @skills
 | 
				
			||||||
 | 
					      if @skills.sets
 | 
				
			||||||
 | 
					        flatSkills = @skills.sets.map((sk) -> sk.skills ).reduce( (a,b) -> a.concat(b) )
 | 
				
			||||||
 | 
					      else if @skills.list
 | 
				
			||||||
 | 
					        flatSkills = flatSkills.concat( this.skills.list.map (sk) -> return sk.name )
 | 
				
			||||||
 | 
					      flatSkills = _.uniq flatSkills
 | 
				
			||||||
 | 
					    flatSkills
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Reset the sheet to an empty state. TODO: refactor/review
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  clear: ( clearMeta ) ->
 | 
				
			||||||
 | 
					    clearMeta = ((clearMeta == undefined) && true) || clearMeta
 | 
				
			||||||
 | 
					    delete this.imp if clearMeta
 | 
				
			||||||
 | 
					    delete this.computed # Don't use Object.keys() here
 | 
				
			||||||
 | 
					    delete this.employment
 | 
				
			||||||
 | 
					    delete this.service
 | 
				
			||||||
 | 
					    delete this.education
 | 
				
			||||||
 | 
					    delete this.recognition
 | 
				
			||||||
 | 
					    delete this.reading
 | 
				
			||||||
 | 
					    delete this.writing
 | 
				
			||||||
 | 
					    delete this.interests
 | 
				
			||||||
 | 
					    delete this.skills
 | 
				
			||||||
 | 
					    delete this.social
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Get a safe count of the number of things in a section.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  count: ( obj ) ->
 | 
				
			||||||
 | 
					    return 0 if !obj
 | 
				
			||||||
 | 
					    return obj.history.length if obj.history
 | 
				
			||||||
 | 
					    return obj.sets.length if obj.sets
 | 
				
			||||||
 | 
					    obj.length || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Add work experience to the sheet. ###
 | 
				
			||||||
 | 
					  add: ( moniker ) ->
 | 
				
			||||||
 | 
					    defSheet = FreshResume.default()
 | 
				
			||||||
 | 
					    newObject =
 | 
				
			||||||
 | 
					      if defSheet[moniker].history
 | 
				
			||||||
 | 
					      then $.extend( true, {}, defSheet[ moniker ].history[0] )
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        if moniker == 'skills'
 | 
				
			||||||
 | 
					        then $.extend( true, {}, defSheet.skills.sets[0] )
 | 
				
			||||||
 | 
					        else $.extend( true, {}, defSheet[ moniker ][0] )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @[ moniker ] = @[ moniker ] || []
 | 
				
			||||||
 | 
					    if @[ moniker ].history
 | 
				
			||||||
 | 
					      @[ moniker ].history.push newObject
 | 
				
			||||||
 | 
					    else if moniker == 'skills'
 | 
				
			||||||
 | 
					      @skills.sets.push newObject
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      @[ moniker ].push newObject
 | 
				
			||||||
 | 
					    newObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Determine if the sheet includes a specific social profile (eg, GitHub).
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  hasProfile: ( socialNetwork ) ->
 | 
				
			||||||
 | 
					    socialNetwork = socialNetwork.trim().toLowerCase()
 | 
				
			||||||
 | 
					    @social && _.some @social, (p) ->
 | 
				
			||||||
 | 
					      p.network.trim().toLowerCase() == socialNetwork
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Return the specified network profile. ###
 | 
				
			||||||
 | 
					  getProfile: ( socialNetwork ) ->
 | 
				
			||||||
 | 
					    socialNetwork = socialNetwork.trim().toLowerCase()
 | 
				
			||||||
 | 
					    @social && _.find @social, (sn) ->
 | 
				
			||||||
 | 
					      sn.network.trim().toLowerCase() == socialNetwork
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Return an array of profiles for the specified network, for when the user
 | 
				
			||||||
 | 
					  has multiple eg. GitHub accounts.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  getProfiles: ( socialNetwork ) ->
 | 
				
			||||||
 | 
					    socialNetwork = socialNetwork.trim().toLowerCase()
 | 
				
			||||||
 | 
					    @social && _.filter @social, (sn) ->
 | 
				
			||||||
 | 
					      sn.network.trim().toLowerCase() == socialNetwork
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Determine if the sheet includes a specific skill. ###
 | 
				
			||||||
 | 
					  hasSkill: ( skill ) ->
 | 
				
			||||||
 | 
					    skill = skill.trim().toLowerCase()
 | 
				
			||||||
 | 
					    @skills && _.some @skills, (sk) ->
 | 
				
			||||||
 | 
					      sk.keywords && _.some sk.keywords, (kw) ->
 | 
				
			||||||
 | 
					        kw.trim().toLowerCase() == skill
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Validate the sheet against the FRESH Resume schema. ###
 | 
				
			||||||
 | 
					  isValid: ( info ) ->
 | 
				
			||||||
 | 
					    schemaObj = require 'fresca'
 | 
				
			||||||
 | 
					    validator = require 'is-my-json-valid'
 | 
				
			||||||
 | 
					    validate = validator( schemaObj, { # See Note [1].
 | 
				
			||||||
 | 
					      formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    ret = validate @
 | 
				
			||||||
 | 
					    if !ret
 | 
				
			||||||
 | 
					      this.imp = this.imp || { };
 | 
				
			||||||
 | 
					      this.imp.validationErrors = validate.errors;
 | 
				
			||||||
 | 
					    ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  duration: (unit) ->
 | 
				
			||||||
 | 
					    super('employment.history', 'start', 'end', unit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Sort dated things on the sheet by start date descending. Assumes that dates
 | 
				
			||||||
 | 
					  on the sheet have been processed with _parseDates().
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  sort: () ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    byDateDesc = (a,b) ->
 | 
				
			||||||
 | 
					      if a.safe.start.isBefore(b.safe.start)
 | 
				
			||||||
 | 
					      then 1
 | 
				
			||||||
 | 
					      else ( if a.safe.start.isAfter(b.safe.start) then -1 else 0 )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sortSection = ( key ) ->
 | 
				
			||||||
 | 
					      ar = __.get this, key
 | 
				
			||||||
 | 
					      if ar && ar.length
 | 
				
			||||||
 | 
					        datedThings = obj.filter (o) -> o.start
 | 
				
			||||||
 | 
					        datedThings.sort( byDateDesc );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sortSection 'employment.history'
 | 
				
			||||||
 | 
					    sortSection 'education.history'
 | 
				
			||||||
 | 
					    sortSection 'service.history'
 | 
				
			||||||
 | 
					    sortSection 'projects'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @writing && @writing.sort (a, b) ->
 | 
				
			||||||
 | 
					      if a.safe.date.isBefore b.safe.date
 | 
				
			||||||
 | 
					      then 1
 | 
				
			||||||
 | 
					      else ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Get the default (starter) sheet.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					FreshResume.default = () ->
 | 
				
			||||||
 | 
					  new FreshResume().parseJSON require('fresh-resume-starter').fresh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Convert the supplied FreshResume to a JSON string, sanitizing meta-properties
 | 
				
			||||||
 | 
					along the way.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					FreshResume.stringify = ( obj ) ->
 | 
				
			||||||
 | 
					  replacer = ( key,value ) -> # Exclude these keys from stringification
 | 
				
			||||||
 | 
					    exKeys = ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
 | 
				
			||||||
 | 
					      'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar']
 | 
				
			||||||
 | 
					    return if _.some( exKeys, (val) -> key.trim() == val )
 | 
				
			||||||
 | 
					    then undefined else value
 | 
				
			||||||
 | 
					  JSON.stringify obj, replacer, 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Convert human-friendly dates into formal Moment.js dates for all collections.
 | 
				
			||||||
 | 
					We don't want to lose the raw textual date as entered by the user, so we store
 | 
				
			||||||
 | 
					the Moment-ified date as a separate property with a prefix of .safe. For ex:
 | 
				
			||||||
 | 
					job.startDate is the date as entered by the user. job.safeStartDate is the
 | 
				
			||||||
 | 
					parsed Moment.js date that we actually use in processing.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					_parseDates = () ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _fmt = require('./fluent-date').fmt
 | 
				
			||||||
 | 
					  that = @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # TODO: refactor recursion
 | 
				
			||||||
 | 
					  replaceDatesInObject = ( obj ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if !obj
 | 
				
			||||||
 | 
					    if Object.prototype.toString.call( obj ) == '[object Array]'
 | 
				
			||||||
 | 
					      obj.forEach (elem) -> replaceDatesInObject( elem )
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    else if typeof obj == 'object'
 | 
				
			||||||
 | 
					      if obj._isAMomentObject || obj.safe
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      Object.keys( obj ).forEach (key) -> replaceDatesInObject obj[key]
 | 
				
			||||||
 | 
					      ['start','end','date'].forEach (val) ->
 | 
				
			||||||
 | 
					        if (obj[val] != undefined) && (!obj.safe || !obj.safe[val])
 | 
				
			||||||
 | 
					          obj.safe = obj.safe || { }
 | 
				
			||||||
 | 
					          obj.safe[ val ] = _fmt obj[val]
 | 
				
			||||||
 | 
					          if obj[val] && (val == 'start') && !obj.end
 | 
				
			||||||
 | 
					            obj.safe.end = _fmt 'current'
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					  Object.keys( this ).forEach (member) ->
 | 
				
			||||||
 | 
					    replaceDatesInObject(that[member])
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###* Export the Sheet function/ctor. ###
 | 
				
			||||||
 | 
					module.exports = FreshResume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
 | 
				
			||||||
 | 
					# in addition to YYYY-MM-DD. The original regex:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#     /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
							
								
								
									
										209
									
								
								src/core/fresh-theme.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/core/fresh-theme.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,209 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the FRESHTheme class.
 | 
				
			||||||
 | 
					@module core/fresh-theme
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FS = require 'fs'
 | 
				
			||||||
 | 
					validator = require 'is-my-json-valid'
 | 
				
			||||||
 | 
					_ = require 'underscore'
 | 
				
			||||||
 | 
					PATH = require 'path'
 | 
				
			||||||
 | 
					parsePath = require 'parse-filepath'
 | 
				
			||||||
 | 
					pathExists = require('path-exists').sync
 | 
				
			||||||
 | 
					EXTEND = require 'extend'
 | 
				
			||||||
 | 
					HMSTATUS = require './status-codes'
 | 
				
			||||||
 | 
					moment = require 'moment'
 | 
				
			||||||
 | 
					loadSafeJson = require '../utils/safe-json-loader'
 | 
				
			||||||
 | 
					READFILES = require 'recursive-readdir-sync'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### A representation of a FRESH theme asset.
 | 
				
			||||||
 | 
					@class FRESHTheme ###
 | 
				
			||||||
 | 
					class FRESHTheme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ### Open and parse the specified theme. ###
 | 
				
			||||||
 | 
					  open: ( themeFolder ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @folder = themeFolder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Open the [theme-name].json file; should have the same name as folder
 | 
				
			||||||
 | 
					    pathInfo = parsePath themeFolder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set up a formats hash for the theme
 | 
				
			||||||
 | 
					    formatsHash = { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Load the theme
 | 
				
			||||||
 | 
					    themeFile = PATH.join themeFolder, 'theme.json'
 | 
				
			||||||
 | 
					    themeInfo = loadSafeJson themeFile
 | 
				
			||||||
 | 
					    if themeInfo.ex
 | 
				
			||||||
 | 
					      throw
 | 
				
			||||||
 | 
					        fluenterror:
 | 
				
			||||||
 | 
					          if themeInfo.ex.operation == 'parse'
 | 
				
			||||||
 | 
					          then HMSTATUS.parseError
 | 
				
			||||||
 | 
					          else HMSTATUS.readError
 | 
				
			||||||
 | 
					      inner: themeInfo.ex.inner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    that = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Move properties from the theme JSON file to the theme object
 | 
				
			||||||
 | 
					    EXTEND true, @, themeInfo.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check for an "inherits" entry in the theme JSON.
 | 
				
			||||||
 | 
					    if @inherits
 | 
				
			||||||
 | 
					      cached = { }
 | 
				
			||||||
 | 
					      _.each @inherits, (th, key) ->
 | 
				
			||||||
 | 
					        themesFolder = require.resolve 'fresh-themes'
 | 
				
			||||||
 | 
					        d = parsePath( themeFolder ).dirname
 | 
				
			||||||
 | 
					        themePath = PATH.join d, th
 | 
				
			||||||
 | 
					        cached[ th ] = cached[th] || new FRESHTheme().open( themePath )
 | 
				
			||||||
 | 
					        formatsHash[ key ] = cached[ th ].getFormat( key )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Load theme files
 | 
				
			||||||
 | 
					    formatsHash = _load.call @, formatsHash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Cache
 | 
				
			||||||
 | 
					    @formats = formatsHash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set the official theme name
 | 
				
			||||||
 | 
					    @name = parsePath( @folder ).name
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ### Determine if the theme supports the specified output format. ###
 | 
				
			||||||
 | 
					  hasFormat: ( fmt ) -> _.has @formats, fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ### Determine if the theme supports the specified output format. ###
 | 
				
			||||||
 | 
					  getFormat: ( fmt ) -> @formats[ fmt ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Load and parse theme source files. ###
 | 
				
			||||||
 | 
					_load = (formatsHash) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  that = @
 | 
				
			||||||
 | 
					  major = false
 | 
				
			||||||
 | 
					  tplFolder = PATH.join @folder, 'src'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Iterate over all files in the theme folder, producing an array, fmts,
 | 
				
			||||||
 | 
					  # containing info for each file. While we're doing that, also build up
 | 
				
			||||||
 | 
					  # the formatsHash object.
 | 
				
			||||||
 | 
					  fmts = READFILES(tplFolder).map (absPath) ->
 | 
				
			||||||
 | 
					    _loadOne.call @, absPath, formatsHash, tplFolder
 | 
				
			||||||
 | 
					  , @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Now, get all the CSS files...
 | 
				
			||||||
 | 
					  @cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # For each CSS file, get its corresponding HTML file. It's possible that
 | 
				
			||||||
 | 
					  # a theme can have a CSS file but *no* HTML file, as when a theme author
 | 
				
			||||||
 | 
					  # creates a pure CSS override of an existing theme.
 | 
				
			||||||
 | 
					  @cssFiles.forEach (cssf) ->
 | 
				
			||||||
 | 
					    idx = _.findIndex fmts, ( fmt ) ->
 | 
				
			||||||
 | 
					      fmt && fmt.pre == cssf.pre && fmt.ext == 'html'
 | 
				
			||||||
 | 
					    cssf.major = false
 | 
				
			||||||
 | 
					    if idx > -1
 | 
				
			||||||
 | 
					      fmts[ idx ].css = cssf.data
 | 
				
			||||||
 | 
					      fmts[ idx ].cssPath = cssf.path
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      if that.inherits
 | 
				
			||||||
 | 
					        # Found a CSS file without an HTML file in a theme that inherits
 | 
				
			||||||
 | 
					        # from another theme. This is the override CSS file.
 | 
				
			||||||
 | 
					        that.overrides = { file: cssf.path, data: cssf.data }
 | 
				
			||||||
 | 
					  formatsHash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Load a single theme file. ###
 | 
				
			||||||
 | 
					_loadOne = ( absPath, formatsHash, tplFolder ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pathInfo = parsePath absPath
 | 
				
			||||||
 | 
					  absPathSafe = absPath.trim().toLowerCase()
 | 
				
			||||||
 | 
					  outFmt = ''
 | 
				
			||||||
 | 
					  act = 'copy'
 | 
				
			||||||
 | 
					  isPrimary = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # If this is an "explicit" theme, all files of importance are specified in
 | 
				
			||||||
 | 
					  # the "transform" section of the theme.json file.
 | 
				
			||||||
 | 
					  if @explicit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outFmt = _.find Object.keys( @formats ), ( fmtKey ) ->
 | 
				
			||||||
 | 
					      fmtVal = @formats[ fmtKey ]
 | 
				
			||||||
 | 
					      _.some fmtVal.transform, (fpath) ->
 | 
				
			||||||
 | 
					        absPathB = PATH.join( @folder, fpath ).trim().toLowerCase()
 | 
				
			||||||
 | 
					        absPathB == absPathSafe
 | 
				
			||||||
 | 
					      , @
 | 
				
			||||||
 | 
					    , @
 | 
				
			||||||
 | 
					    act = 'transform' if outFmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if !outFmt
 | 
				
			||||||
 | 
					    # If this file lives in a specific format folder within the theme,
 | 
				
			||||||
 | 
					    # such as "/latex" or "/html", then that format is the implicit output
 | 
				
			||||||
 | 
					    # format for all files within the folder
 | 
				
			||||||
 | 
					    portion = pathInfo.dirname.replace tplFolder,''
 | 
				
			||||||
 | 
					    if portion && portion.trim()
 | 
				
			||||||
 | 
					      return if portion[1] == '_'
 | 
				
			||||||
 | 
					      reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig
 | 
				
			||||||
 | 
					      res = reg.exec( portion )
 | 
				
			||||||
 | 
					      if res
 | 
				
			||||||
 | 
					        if res[1] != 'partials'
 | 
				
			||||||
 | 
					          outFmt = res[1]
 | 
				
			||||||
 | 
					          act = 'transform' if !@explicit
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          @partials = @partials || []
 | 
				
			||||||
 | 
					          @partials.push( { name: pathInfo.name, path: absPath } )
 | 
				
			||||||
 | 
					          return null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Otherwise, the output format is inferred from the filename, as in
 | 
				
			||||||
 | 
					  # compact-[outputformat].[extension], for ex, compact-pdf.html
 | 
				
			||||||
 | 
					  if !outFmt
 | 
				
			||||||
 | 
					    idx = pathInfo.name.lastIndexOf '-'
 | 
				
			||||||
 | 
					    outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr idx+1
 | 
				
			||||||
 | 
					    act = 'transform' if !@explicit
 | 
				
			||||||
 | 
					    defFormats = require './default-formats'
 | 
				
			||||||
 | 
					    isPrimary = _.some defFormats, (form) ->
 | 
				
			||||||
 | 
					      form.name == outFmt and pathInfo.extname != '.css'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Make sure we have a valid formatsHash
 | 
				
			||||||
 | 
					  formatsHash[ outFmt ] = formatsHash[outFmt] || {
 | 
				
			||||||
 | 
					    outFormat: outFmt,
 | 
				
			||||||
 | 
					    files: []
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Move symlink descriptions from theme.json to the format
 | 
				
			||||||
 | 
					  if @formats?[ outFmt ]?.symLinks
 | 
				
			||||||
 | 
					    formatsHash[ outFmt ].symLinks = @formats[ outFmt ].symLinks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Create the file representation object
 | 
				
			||||||
 | 
					  obj =
 | 
				
			||||||
 | 
					    action: act
 | 
				
			||||||
 | 
					    primary: isPrimary
 | 
				
			||||||
 | 
					    path: absPath
 | 
				
			||||||
 | 
					    orgPath: PATH.relative tplFolder, absPath
 | 
				
			||||||
 | 
					    ext: pathInfo.extname.slice 1
 | 
				
			||||||
 | 
					    title: friendlyName outFmt
 | 
				
			||||||
 | 
					    pre: outFmt
 | 
				
			||||||
 | 
					    # outFormat: outFmt || pathInfo.name,
 | 
				
			||||||
 | 
					    data: FS.readFileSync absPath, 'utf8'
 | 
				
			||||||
 | 
					    css: null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Add this file to the list of files for this format type.
 | 
				
			||||||
 | 
					  formatsHash[ outFmt ].files.push( obj )
 | 
				
			||||||
 | 
					  obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Return a more friendly name for certain formats. ###
 | 
				
			||||||
 | 
					friendlyName = ( val ) ->
 | 
				
			||||||
 | 
					  val = (val && val.trim().toLowerCase()) || ''
 | 
				
			||||||
 | 
					  friendly = { yml: 'yaml', md: 'markdown', txt: 'text' }
 | 
				
			||||||
 | 
					  friendly[val] || val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = FRESHTheme
 | 
				
			||||||
							
								
								
									
										337
									
								
								src/core/jrs-resume.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								src/core/jrs-resume.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,337 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the JRSResume class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/jrs-resume
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FS = require('fs')
 | 
				
			||||||
 | 
					extend = require('extend')
 | 
				
			||||||
 | 
					validator = require('is-my-json-valid')
 | 
				
			||||||
 | 
					_ = require('underscore')
 | 
				
			||||||
 | 
					PATH = require('path')
 | 
				
			||||||
 | 
					MD = require('marked')
 | 
				
			||||||
 | 
					CONVERTER = require('fresh-jrs-converter')
 | 
				
			||||||
 | 
					moment = require('moment')
 | 
				
			||||||
 | 
					AbstractResume = require('./abstract-resume')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					A JRS resume or CV. JRS resumes are backed by JSON, and each JRSResume object
 | 
				
			||||||
 | 
					is an instantiation of that JSON decorated with utility methods.
 | 
				
			||||||
 | 
					@class JRSResume
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					class JRSResume extends AbstractResume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Initialize the the JSResume from string. ###
 | 
				
			||||||
 | 
					  parse: ( stringData, opts ) ->
 | 
				
			||||||
 | 
					    @imp = @imp ? raw: stringData
 | 
				
			||||||
 | 
					    this.parseJSON JSON.parse( stringData ), opts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Initialize the JRSResume object from JSON.
 | 
				
			||||||
 | 
					  Open and parse the specified JRS resume. Merge the JSON object model onto
 | 
				
			||||||
 | 
					  this Sheet instance with extend() and convert sheet dates to a safe &
 | 
				
			||||||
 | 
					  consistent format. Then sort each section by startDate descending.
 | 
				
			||||||
 | 
					  @param rep {Object} The raw JSON representation.
 | 
				
			||||||
 | 
					  @param opts {Object} Resume loading and parsing options.
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    date: Perform safe date conversion.
 | 
				
			||||||
 | 
					    sort: Sort resume items by date.
 | 
				
			||||||
 | 
					    compute: Prepare computed resume totals.
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  parseJSON: ( rep, opts ) ->
 | 
				
			||||||
 | 
					    opts = opts || { };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ignore any element with the 'ignore: true' designator.
 | 
				
			||||||
 | 
					    that = this
 | 
				
			||||||
 | 
					    traverse = require 'traverse'
 | 
				
			||||||
 | 
					    ignoreList = []
 | 
				
			||||||
 | 
					    scrubbed = traverse( rep ).map ( x ) ->
 | 
				
			||||||
 | 
					      if !@isLeaf && @node.ignore
 | 
				
			||||||
 | 
					        if  @node.ignore == true || this.node.ignore == 'true'
 | 
				
			||||||
 | 
					          ignoreList.push @node
 | 
				
			||||||
 | 
					          @remove()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Extend resume properties onto ourself.
 | 
				
			||||||
 | 
					    extend true, this, scrubbed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set up metadata
 | 
				
			||||||
 | 
					    if !@imp?.processed
 | 
				
			||||||
 | 
					      # Set up metadata TODO: Clean up metadata on the object model.
 | 
				
			||||||
 | 
					      opts = opts || { }
 | 
				
			||||||
 | 
					      if opts.imp == undefined || opts.imp
 | 
				
			||||||
 | 
					        @imp = @imp || { }
 | 
				
			||||||
 | 
					        @imp.title = (opts.title || @imp.title) || @basics.name
 | 
				
			||||||
 | 
					        unless @imp.raw
 | 
				
			||||||
 | 
					          @imp.raw = JSON.stringify rep
 | 
				
			||||||
 | 
					      @imp.processed = true
 | 
				
			||||||
 | 
					    # Parse dates, sort dates, and calculate computed values
 | 
				
			||||||
 | 
					    (opts.date == undefined || opts.date) && _parseDates.call( this )
 | 
				
			||||||
 | 
					    (opts.sort == undefined || opts.sort) && this.sort()
 | 
				
			||||||
 | 
					    if opts.compute == undefined || opts.compute
 | 
				
			||||||
 | 
					      @basics.computed =
 | 
				
			||||||
 | 
					        numYears: this.duration()
 | 
				
			||||||
 | 
					        keywords: this.keywords()
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Save the sheet to disk (for environments that have disk access). ###
 | 
				
			||||||
 | 
					  save: ( filename ) ->
 | 
				
			||||||
 | 
					    @imp.file = filename || @imp.file
 | 
				
			||||||
 | 
					    FS.writeFileSync @imp.file, @stringify( this ), 'utf8'
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Save the sheet to disk in a specific format, either FRESH or JRS. ###
 | 
				
			||||||
 | 
					  saveAs: ( filename, format ) ->
 | 
				
			||||||
 | 
					    if format == 'JRS'
 | 
				
			||||||
 | 
					      @imp.file = filename || @imp.file;
 | 
				
			||||||
 | 
					      FS.writeFileSync( @imp.file, @stringify(), 'utf8' );
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      newRep = CONVERTER.toFRESH @
 | 
				
			||||||
 | 
					      stringRep = CONVERTER.toSTRING newRep
 | 
				
			||||||
 | 
					      FS.writeFileSync filename, stringRep, 'utf8'
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Return the resume format. ###
 | 
				
			||||||
 | 
					  format: () -> 'JRS'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  stringify: () -> JRSResume.stringify( @ )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Return a unique list of all keywords across all skills. ###
 | 
				
			||||||
 | 
					  keywords: () ->
 | 
				
			||||||
 | 
					    flatSkills = []
 | 
				
			||||||
 | 
					    if @skills && this.skills.length
 | 
				
			||||||
 | 
					      @skills.forEach ( s ) -> flatSkills = _.union flatSkills, s.keywords
 | 
				
			||||||
 | 
					    flatSkills
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Return internal metadata. Create if it doesn't exist.
 | 
				
			||||||
 | 
					  JSON Resume v0.0.0 doesn't allow additional properties at the root level,
 | 
				
			||||||
 | 
					  so tuck this into the .basic sub-object.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  i: () ->
 | 
				
			||||||
 | 
					    @imp = @imp ? { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Reset the sheet to an empty state. ###
 | 
				
			||||||
 | 
					  clear = ( clearMeta ) ->
 | 
				
			||||||
 | 
					    clearMeta = ((clearMeta == undefined) && true) || clearMeta;
 | 
				
			||||||
 | 
					    delete this.imp if clearMeta
 | 
				
			||||||
 | 
					    delete this.basics.computed # Don't use Object.keys() here
 | 
				
			||||||
 | 
					    delete this.work
 | 
				
			||||||
 | 
					    delete this.volunteer
 | 
				
			||||||
 | 
					    delete this.education
 | 
				
			||||||
 | 
					    delete this.awards
 | 
				
			||||||
 | 
					    delete this.publications
 | 
				
			||||||
 | 
					    delete this.interests
 | 
				
			||||||
 | 
					    delete this.skills
 | 
				
			||||||
 | 
					    delete this.basics.profiles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Add work experience to the sheet. ###
 | 
				
			||||||
 | 
					  add: ( moniker ) ->
 | 
				
			||||||
 | 
					    defSheet = JRSResume.default()
 | 
				
			||||||
 | 
					    newObject = $.extend( true, {}, defSheet[ moniker ][0] )
 | 
				
			||||||
 | 
					    this[ moniker ] = this[ moniker ] || []
 | 
				
			||||||
 | 
					    this[ moniker ].push( newObject )
 | 
				
			||||||
 | 
					    newObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Determine if the sheet includes a specific social profile (eg, GitHub). ###
 | 
				
			||||||
 | 
					  hasProfile: ( socialNetwork ) ->
 | 
				
			||||||
 | 
					    socialNetwork = socialNetwork.trim().toLowerCase()
 | 
				
			||||||
 | 
					    return @basics.profiles && _.some @basics.profiles, (p) ->
 | 
				
			||||||
 | 
					      return p.network.trim().toLowerCase() == socialNetwork
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Determine if the sheet includes a specific skill. ###
 | 
				
			||||||
 | 
					  hasSkill: ( skill ) ->
 | 
				
			||||||
 | 
					    skill = skill.trim().toLowerCase()
 | 
				
			||||||
 | 
					    return this.skills && _.some this.skills, (sk) ->
 | 
				
			||||||
 | 
					      return sk.keywords && _.some sk.keywords, (kw) ->
 | 
				
			||||||
 | 
					        kw.trim().toLowerCase() == skill
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Validate the sheet against the JSON Resume schema. ###
 | 
				
			||||||
 | 
					  isValid: ( ) -> # TODO: ↓ fix this path ↓
 | 
				
			||||||
 | 
					    schema = FS.readFileSync PATH.join( __dirname, 'resume.json' ), 'utf8'
 | 
				
			||||||
 | 
					    schemaObj = JSON.parse schema
 | 
				
			||||||
 | 
					    validator = require 'is-my-json-valid'
 | 
				
			||||||
 | 
					    validate = validator( schemaObj, { # Note [1]
 | 
				
			||||||
 | 
					      formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    temp = @imp
 | 
				
			||||||
 | 
					    delete @imp
 | 
				
			||||||
 | 
					    ret = validate @
 | 
				
			||||||
 | 
					    @imp = temp
 | 
				
			||||||
 | 
					    if !ret
 | 
				
			||||||
 | 
					      @imp = @imp || { };
 | 
				
			||||||
 | 
					      @imp.validationErrors = validate.errors;
 | 
				
			||||||
 | 
					    ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  duration: (unit) ->
 | 
				
			||||||
 | 
					    super('work', 'startDate', 'endDate', unit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Sort dated things on the sheet by start date descending. Assumes that dates
 | 
				
			||||||
 | 
					  on the sheet have been processed with _parseDates().
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  sort: ( ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    byDateDesc = (a,b) ->
 | 
				
			||||||
 | 
					      if a.safeStartDate.isBefore(b.safeStartDate)
 | 
				
			||||||
 | 
					      then 1
 | 
				
			||||||
 | 
					      else ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @work && @work.sort byDateDesc
 | 
				
			||||||
 | 
					    @education && @education.sort byDateDesc
 | 
				
			||||||
 | 
					    @volunteer && @volunteer.sort byDateDesc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @awards && @awards.sort (a, b) ->
 | 
				
			||||||
 | 
					      if a.safeDate.isBefore b.safeDate
 | 
				
			||||||
 | 
					      then 1
 | 
				
			||||||
 | 
					      else (a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @publications && @publications.sort (a, b) ->
 | 
				
			||||||
 | 
					      if ( a.safeReleaseDate.isBefore(b.safeReleaseDate) )
 | 
				
			||||||
 | 
					      then 1
 | 
				
			||||||
 | 
					      else ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dupe: () ->
 | 
				
			||||||
 | 
					    rnew = new JRSResume()
 | 
				
			||||||
 | 
					    rnew.parse this.stringify(), { }
 | 
				
			||||||
 | 
					    rnew
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Create a copy of this resume in which all fields have been interpreted as
 | 
				
			||||||
 | 
					  Markdown.
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  harden: () ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    that = @
 | 
				
			||||||
 | 
					    ret = @dupe()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HD = (txt) -> '@@@@~' + txt + '~@@@@'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HDIN = (txt) ->
 | 
				
			||||||
 | 
					      #return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
 | 
				
			||||||
 | 
					      return HD txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: refactor recursion
 | 
				
			||||||
 | 
					    hardenStringsInObject = ( obj, inline ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return if !obj
 | 
				
			||||||
 | 
					      inline = inline == undefined || inline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if Object.prototype.toString.call( obj ) == '[object Array]'
 | 
				
			||||||
 | 
					        obj.forEach (elem, idx, ar) ->
 | 
				
			||||||
 | 
					          if typeof elem == 'string' || elem instanceof String
 | 
				
			||||||
 | 
					            ar[idx] = if inline then HDIN(elem) else HD( elem )
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            hardenStringsInObject elem
 | 
				
			||||||
 | 
					      else if typeof obj == 'object'
 | 
				
			||||||
 | 
					        Object.keys( obj ).forEach (key) ->
 | 
				
			||||||
 | 
					          sub = obj[key]
 | 
				
			||||||
 | 
					          if typeof sub == 'string' || sub instanceof String
 | 
				
			||||||
 | 
					            if _.contains(['skills','url','website','startDate','endDate',
 | 
				
			||||||
 | 
					              'releaseDate','date','phone','email','address','postalCode',
 | 
				
			||||||
 | 
					              'city','country','region'], key)
 | 
				
			||||||
 | 
					              return
 | 
				
			||||||
 | 
					            if key == 'summary'
 | 
				
			||||||
 | 
					              obj[key] = HD( obj[key] )
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              obj[key] = if inline then HDIN( obj[key] ) else HD( obj[key] )
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            hardenStringsInObject sub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Object.keys( ret ).forEach (member) ->
 | 
				
			||||||
 | 
					      hardenStringsInObject ret[ member ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###* Get the default (empty) sheet. ###
 | 
				
			||||||
 | 
					JRSResume.default = () ->
 | 
				
			||||||
 | 
					  new JRSResume().parseJSON require('fresh-resume-starter').jrs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Convert this object to a JSON string, sanitizing meta-properties along the
 | 
				
			||||||
 | 
					way. Don't override .toString().
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					JRSResume.stringify = ( obj ) ->
 | 
				
			||||||
 | 
					  replacer = ( key,value ) -> # Exclude these keys from stringification
 | 
				
			||||||
 | 
					    temp = _.some ['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
 | 
				
			||||||
 | 
					      'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
 | 
				
			||||||
 | 
					      'isModified', 'htmlPreview', 'display_progress_bar'],
 | 
				
			||||||
 | 
					      ( val ) -> return key.trim() == val
 | 
				
			||||||
 | 
					    return if temp then undefined else value
 | 
				
			||||||
 | 
					  JSON.stringify obj, replacer, 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Convert human-friendly dates into formal Moment.js dates for all collections.
 | 
				
			||||||
 | 
					We don't want to lose the raw textual date as entered by the user, so we store
 | 
				
			||||||
 | 
					the Moment-ified date as a separate property with a prefix of .safe. For ex:
 | 
				
			||||||
 | 
					job.startDate is the date as entered by the user. job.safeStartDate is the
 | 
				
			||||||
 | 
					parsed Moment.js date that we actually use in processing.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					_parseDates = () ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _fmt = require('./fluent-date').fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @work && @work.forEach (job) ->
 | 
				
			||||||
 | 
					    job.safeStartDate = _fmt( job.startDate )
 | 
				
			||||||
 | 
					    job.safeEndDate = _fmt( job.endDate )
 | 
				
			||||||
 | 
					  @education && @education.forEach (edu) ->
 | 
				
			||||||
 | 
					    edu.safeStartDate = _fmt( edu.startDate )
 | 
				
			||||||
 | 
					    edu.safeEndDate = _fmt( edu.endDate )
 | 
				
			||||||
 | 
					  @volunteer && @volunteer.forEach (vol) ->
 | 
				
			||||||
 | 
					    vol.safeStartDate = _fmt( vol.startDate )
 | 
				
			||||||
 | 
					    vol.safeEndDate = _fmt( vol.endDate )
 | 
				
			||||||
 | 
					  @awards && @awards.forEach (awd) ->
 | 
				
			||||||
 | 
					    awd.safeDate = _fmt( awd.date )
 | 
				
			||||||
 | 
					  @publications && @publications.forEach (pub) ->
 | 
				
			||||||
 | 
					    pub.safeReleaseDate = _fmt( pub.releaseDate )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					Export the JRSResume function/ctor.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					module.exports = JRSResume
 | 
				
			||||||
							
								
								
									
										88
									
								
								src/core/jrs-theme.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/core/jrs-theme.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the JRSTheme class.
 | 
				
			||||||
 | 
					@module core/jrs-theme
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.MD for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_ = require 'underscore'
 | 
				
			||||||
 | 
					PATH = require 'path'
 | 
				
			||||||
 | 
					parsePath = require 'parse-filepath'
 | 
				
			||||||
 | 
					pathExists = require('path-exists').sync
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					The JRSTheme class is a representation of a JSON Resume theme asset.
 | 
				
			||||||
 | 
					@class JRSTheme
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					class JRSTheme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Open and parse the specified theme.
 | 
				
			||||||
 | 
					  @method open
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  open: ( thFolder ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @folder = thFolder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Open the [theme-name].json file; should have the same
 | 
				
			||||||
 | 
					    # name as folder
 | 
				
			||||||
 | 
					    pathInfo = parsePath thFolder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Open and parse the theme's package.json file.
 | 
				
			||||||
 | 
					    pkgJsonPath = PATH.join thFolder, 'package.json'
 | 
				
			||||||
 | 
					    if pathExists pkgJsonPath
 | 
				
			||||||
 | 
					      thApi = require thFolder
 | 
				
			||||||
 | 
					      thPkg = require pkgJsonPath
 | 
				
			||||||
 | 
					      this.name = thPkg.name
 | 
				
			||||||
 | 
					      this.render = (thApi && thApi.render) || undefined
 | 
				
			||||||
 | 
					      this.engine = 'jrs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Create theme formats (HTML and PDF). Just add the bare minimum mix of
 | 
				
			||||||
 | 
					      # properties necessary to allow JSON Resume themes to share a rendering
 | 
				
			||||||
 | 
					      # path with FRESH themes.
 | 
				
			||||||
 | 
					      this.formats =
 | 
				
			||||||
 | 
					        html:
 | 
				
			||||||
 | 
					          outFormat: 'html'
 | 
				
			||||||
 | 
					          files: [{
 | 
				
			||||||
 | 
					            action: 'transform',
 | 
				
			||||||
 | 
					            render: this.render,
 | 
				
			||||||
 | 
					            primary: true,
 | 
				
			||||||
 | 
					            ext: 'html',
 | 
				
			||||||
 | 
					            css: null
 | 
				
			||||||
 | 
					          }]
 | 
				
			||||||
 | 
					        pdf:
 | 
				
			||||||
 | 
					          outFormat: 'pdf'
 | 
				
			||||||
 | 
					          files: [{
 | 
				
			||||||
 | 
					            action: 'transform',
 | 
				
			||||||
 | 
					            render: this.render,
 | 
				
			||||||
 | 
					            primary: true,
 | 
				
			||||||
 | 
					            ext: 'pdf',
 | 
				
			||||||
 | 
					            css: null
 | 
				
			||||||
 | 
					          }]
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      throw { fluenterror: HACKMYSTATUS.missingPackageJSON };
 | 
				
			||||||
 | 
					    @
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Determine if the theme supports the output format.
 | 
				
			||||||
 | 
					  @method hasFormat
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  hasFormat: ( fmt ) ->  _.has this.formats, fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Return the requested output format.
 | 
				
			||||||
 | 
					  @method getFormat
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  getFormat: ( fmt ) -> @formats[ fmt ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = JRSTheme;
 | 
				
			||||||
							
								
								
									
										109
									
								
								src/core/resume-factory.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/core/resume-factory.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Definition of the ResumeFactory class.
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.md for details.
 | 
				
			||||||
 | 
					@module core/resume-factory
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FS              = require('fs')
 | 
				
			||||||
 | 
					HACKMYSTATUS    = require('./status-codes')
 | 
				
			||||||
 | 
					HME             = require('./event-codes')
 | 
				
			||||||
 | 
					ResumeConverter = require('fresh-jrs-converter')
 | 
				
			||||||
 | 
					chalk           = require('chalk')
 | 
				
			||||||
 | 
					SyntaxErrorEx   = require('../utils/syntax-error-ex')
 | 
				
			||||||
 | 
					_               = require('underscore')
 | 
				
			||||||
 | 
					require('string.prototype.startswith')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###*
 | 
				
			||||||
 | 
					A simple factory class for FRESH and JSON Resumes.
 | 
				
			||||||
 | 
					@class ResumeFactory
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ResumeFactory = module.exports =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###*
 | 
				
			||||||
 | 
					  Load one or more resumes from disk.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @param {Object} opts An options object with settings for the factory as well
 | 
				
			||||||
 | 
					  as passthrough settings for FRESHResume or JRSResume. Structure:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        format: 'FRESH',    // Format to open as. ('FRESH', 'JRS', null)
 | 
				
			||||||
 | 
					        objectify: true,    // FRESH/JRSResume or raw JSON?
 | 
				
			||||||
 | 
					        inner: {            // Passthru options for FRESH/JRSResume
 | 
				
			||||||
 | 
					          sort: false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###
 | 
				
			||||||
 | 
					  load: ( sources, opts, emitter ) ->
 | 
				
			||||||
 | 
					    sources.map( (src) ->
 | 
				
			||||||
 | 
					      @loadOne( src, opts, emitter )
 | 
				
			||||||
 | 
					    , @)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ###* Load a single resume from disk.  ###
 | 
				
			||||||
 | 
					  loadOne: ( src, opts, emitter ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    toFormat = opts.format     # Can be null
 | 
				
			||||||
 | 
					    objectify = opts.objectify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get the destination format. Can be 'fresh', 'jrs', or null/undefined.
 | 
				
			||||||
 | 
					    toFormat && (toFormat = toFormat.toLowerCase().trim())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Load and parse the resume JSON
 | 
				
			||||||
 | 
					    info = _parse src, opts, emitter
 | 
				
			||||||
 | 
					    return info if info.fluenterror
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Determine the resume format: FRESH or JRS
 | 
				
			||||||
 | 
					    json = info.json
 | 
				
			||||||
 | 
					    isFRESH = json.meta && json.meta.format && json.meta.format.startsWith('FRESH@');
 | 
				
			||||||
 | 
					    orgFormat = if isFRESH then 'fresh' else 'jrs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Convert between formats if necessary
 | 
				
			||||||
 | 
					    if toFormat and ( orgFormat != toFormat )
 | 
				
			||||||
 | 
					      json = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( json )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Objectify the resume, that is, convert it from JSON to a FRESHResume
 | 
				
			||||||
 | 
					    # or JRSResume object.
 | 
				
			||||||
 | 
					    rez = null
 | 
				
			||||||
 | 
					    if objectify
 | 
				
			||||||
 | 
					      ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume');
 | 
				
			||||||
 | 
					      rez = new ResumeClass().parseJSON( json, opts.inner );
 | 
				
			||||||
 | 
					      rez.i().file = src;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file: src
 | 
				
			||||||
 | 
					    json: info.json
 | 
				
			||||||
 | 
					    rez: rez
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_parse = ( fileName, opts, eve ) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rawData = null
 | 
				
			||||||
 | 
					  try
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Read the file
 | 
				
			||||||
 | 
					    eve && eve.stat( HME.beforeRead, { file: fileName });
 | 
				
			||||||
 | 
					    rawData = FS.readFileSync( fileName, 'utf8' );
 | 
				
			||||||
 | 
					    eve && eve.stat( HME.afterRead, { file: fileName, data: rawData });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Parse the file
 | 
				
			||||||
 | 
					    eve && eve.stat HME.beforeParse, { data: rawData }
 | 
				
			||||||
 | 
					    ret = { json: JSON.parse( rawData ) }
 | 
				
			||||||
 | 
					    orgFormat =
 | 
				
			||||||
 | 
					      if ret.json.meta && ret.json.meta.format && ret.json.meta.format.startsWith('FRESH@')
 | 
				
			||||||
 | 
					      then 'fresh' else 'jrs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eve && eve.stat HME.afterParse, { file: fileName, data: ret.json, fmt: orgFormat }
 | 
				
			||||||
 | 
					    return ret
 | 
				
			||||||
 | 
					  catch
 | 
				
			||||||
 | 
					    # Can be ENOENT, EACCES, SyntaxError, etc.
 | 
				
			||||||
 | 
					    fluenterror: if rawData then HACKMYSTATUS.parseError else HACKMYSTATUS.readError
 | 
				
			||||||
 | 
					    inner: _error
 | 
				
			||||||
 | 
					    raw: rawData
 | 
				
			||||||
 | 
					    file: fileName
 | 
				
			||||||
@@ -1,259 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
Abstract character/resume sheet representation.
 | 
					 | 
				
			||||||
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
(function() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var FS = require('fs')
 | 
					 | 
				
			||||||
    , extend = require('../utils/extend')
 | 
					 | 
				
			||||||
    , validator = require('is-my-json-valid')
 | 
					 | 
				
			||||||
    , _ = require('underscore')
 | 
					 | 
				
			||||||
    , PATH = require('path')
 | 
					 | 
				
			||||||
    , moment = require('moment');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  The Sheet class represent a specific JSON character sheet. When Sheet.open
 | 
					 | 
				
			||||||
  is called, we merge the loaded JSON sheet properties onto the Sheet instance
 | 
					 | 
				
			||||||
  via extend(), so a full-grown sheet object will have all of the methods here,
 | 
					 | 
				
			||||||
  plus a complement of JSON properties from the backing JSON file. That allows
 | 
					 | 
				
			||||||
  us to treat Sheet objects interchangeably with the loaded JSON model.
 | 
					 | 
				
			||||||
  @class Sheet
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  function Sheet() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Open and parse the specified JSON resume sheet. Merge the JSON object model
 | 
					 | 
				
			||||||
  onto this Sheet instance with extend() and convert sheet dates to a safe &
 | 
					 | 
				
			||||||
  consistent format. Then sort each section by startDate descending.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.open = function( file, title ) {
 | 
					 | 
				
			||||||
    this.meta = { fileName: file };
 | 
					 | 
				
			||||||
    this.meta.raw = FS.readFileSync( file, 'utf8' );
 | 
					 | 
				
			||||||
    return this.parse( this.meta.raw, title );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Save the sheet to disk (for environments that have disk access).
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.save = function( filename ) {
 | 
					 | 
				
			||||||
    filename = filename || this.meta.fileName;
 | 
					 | 
				
			||||||
    FS.writeFileSync( filename, this.stringify(), 'utf8' );
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Convert this object to a JSON string, sanitizing meta-properties along the
 | 
					 | 
				
			||||||
  way. Don't override .toString().
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.stringify = function() {
 | 
					 | 
				
			||||||
    function replacer( key,value ) { // Exclude these keys from stringification
 | 
					 | 
				
			||||||
      return _.some(['meta', 'warnings', 'computed', 'filt', 'ctrl', 'index',
 | 
					 | 
				
			||||||
        'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result',
 | 
					 | 
				
			||||||
      'isModified', 'htmlPreview'],
 | 
					 | 
				
			||||||
        function( val ) { return key.trim() === val; }
 | 
					 | 
				
			||||||
      ) ? undefined : value;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return JSON.stringify( this, replacer, 2 );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Open and parse the specified JSON resume sheet. Merge the JSON object model
 | 
					 | 
				
			||||||
  onto this Sheet instance with extend() and convert sheet dates to a safe &
 | 
					 | 
				
			||||||
  consistent format. Then sort each section by startDate descending.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.parse = function( stringData, opts ) {
 | 
					 | 
				
			||||||
    opts = opts || { };
 | 
					 | 
				
			||||||
    var rep = JSON.parse( stringData );
 | 
					 | 
				
			||||||
    extend( true, this, rep );
 | 
					 | 
				
			||||||
    // Set up metadata
 | 
					 | 
				
			||||||
    if( opts.meta === undefined || opts.meta ) {
 | 
					 | 
				
			||||||
      this.meta = this.meta || { };
 | 
					 | 
				
			||||||
      this.meta.title = (opts.title || this.meta.title) || this.basics.name;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // Parse dates, sort dates, and calculate computed values
 | 
					 | 
				
			||||||
    (opts.date === undefined || opts.date) && _parseDates.call( this );
 | 
					 | 
				
			||||||
    (opts.sort === undefined || opts.sort) && this.sort();
 | 
					 | 
				
			||||||
    (opts.compute === undefined || opts.compute) && (this.computed = {
 | 
					 | 
				
			||||||
      numYears: this.duration(),
 | 
					 | 
				
			||||||
      keywords: this.keywords()
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Return a unique list of all keywords across all skills.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.keywords = function() {
 | 
					 | 
				
			||||||
    var flatSkills = [];
 | 
					 | 
				
			||||||
    if( this.skills && this.skills.length ) {
 | 
					 | 
				
			||||||
      this.skills.forEach( function( s ) {
 | 
					 | 
				
			||||||
        flatSkills = _.union( flatSkills, s.keywords );
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return flatSkills;
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Update the sheet's raw data. TODO: remove/refactor
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.updateData = function( str ) {
 | 
					 | 
				
			||||||
    this.clear( false );
 | 
					 | 
				
			||||||
    this.parse( str )
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Reset the sheet to an empty state.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.clear = function( clearMeta ) {
 | 
					 | 
				
			||||||
    clearMeta = ((clearMeta === undefined) && true) || clearMeta;
 | 
					 | 
				
			||||||
    clearMeta && (delete this.meta);
 | 
					 | 
				
			||||||
    delete this.computed; // Don't use Object.keys() here
 | 
					 | 
				
			||||||
    delete this.work;
 | 
					 | 
				
			||||||
    delete this.volunteer;
 | 
					 | 
				
			||||||
    delete this.education;
 | 
					 | 
				
			||||||
    delete this.awards;
 | 
					 | 
				
			||||||
    delete this.publications;
 | 
					 | 
				
			||||||
    delete this.interests;
 | 
					 | 
				
			||||||
    delete this.skills;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Get the default (empty) sheet.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.default = function() {
 | 
					 | 
				
			||||||
    return new Sheet().open( PATH.join( __dirname, 'empty.json'), 'Empty' );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Add work experience to the sheet.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.add = function( moniker ) {
 | 
					 | 
				
			||||||
    var defSheet = Sheet.default();
 | 
					 | 
				
			||||||
    var newObject = $.extend( true, {}, defSheet[ moniker ][0] );
 | 
					 | 
				
			||||||
    this[ moniker ] = this[ moniker ] || [];
 | 
					 | 
				
			||||||
    this[ moniker ].push( newObject );
 | 
					 | 
				
			||||||
    return newObject;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Determine if the sheet includes a specific social profile (eg, GitHub).
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.hasProfile = function( socialNetwork ) {
 | 
					 | 
				
			||||||
    socialNetwork = socialNetwork.trim().toLowerCase();
 | 
					 | 
				
			||||||
    return this.basics.profiles && _.some( this.basics.profiles, function(p) {
 | 
					 | 
				
			||||||
      return p.network.trim().toLowerCase() === socialNetwork;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Determine if the sheet includes a specific skill.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.hasSkill = function( skill ) {
 | 
					 | 
				
			||||||
    skill = skill.trim().toLowerCase();
 | 
					 | 
				
			||||||
    return this.skills && _.some( this.skills, function(sk) {
 | 
					 | 
				
			||||||
      return sk.keywords && _.some( sk.keywords, function(kw) {
 | 
					 | 
				
			||||||
        return kw.trim().toLowerCase() === skill;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Validate the sheet against the JSON Resume schema.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.isValid = function( ) { // TODO: ↓ fix this path ↓
 | 
					 | 
				
			||||||
    var schema = FS.readFileSync( PATH.join( __dirname, 'resume.json' ), 'utf8' );
 | 
					 | 
				
			||||||
    var schemaObj = JSON.parse( schema );
 | 
					 | 
				
			||||||
    var validator = require('is-my-json-valid')
 | 
					 | 
				
			||||||
    var validate = validator( schemaObj );
 | 
					 | 
				
			||||||
    return validate( this );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Calculate the total duration of the sheet. Assumes this.work has been sorted
 | 
					 | 
				
			||||||
  by start date descending, perhaps via a call to Sheet.sort().
 | 
					 | 
				
			||||||
  @returns The total duration of the sheet's work history, that is, the number
 | 
					 | 
				
			||||||
  of years between the start date of the earliest job on the resume and the
 | 
					 | 
				
			||||||
  *latest end date of all jobs in the work history*. This last condition is for
 | 
					 | 
				
			||||||
  sheets that have overlapping jobs.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.duration = function() {
 | 
					 | 
				
			||||||
    if( this.work && this.work.length ) {
 | 
					 | 
				
			||||||
      var careerStart = this.work[ this.work.length - 1].safeStartDate;
 | 
					 | 
				
			||||||
      if ((typeof careerStart === 'string' || careerStart instanceof String) &&
 | 
					 | 
				
			||||||
          !careerStart.trim())
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
      var careerLast = _.max( this.work, function( w ) {
 | 
					 | 
				
			||||||
        return w.safeEndDate.unix();
 | 
					 | 
				
			||||||
      }).safeEndDate;
 | 
					 | 
				
			||||||
      return careerLast.diff( careerStart, 'years' );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Sort dated things on the sheet by start date descending. Assumes that dates
 | 
					 | 
				
			||||||
  on the sheet have been processed with _parseDates().
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Sheet.prototype.sort = function( ) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.work && this.work.sort( byDateDesc );
 | 
					 | 
				
			||||||
    this.education && this.education.sort( byDateDesc );
 | 
					 | 
				
			||||||
    this.volunteer && this.volunteer.sort( byDateDesc );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.awards && this.awards.sort( function(a, b) {
 | 
					 | 
				
			||||||
      return( a.safeDate.isBefore(b.safeDate) ) ? 1
 | 
					 | 
				
			||||||
        : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    this.publications && this.publications.sort( function(a, b) {
 | 
					 | 
				
			||||||
      return( a.safeReleaseDate.isBefore(b.safeReleaseDate) ) ? 1
 | 
					 | 
				
			||||||
        : ( a.safeReleaseDate.isAfter(b.safeReleaseDate) && -1 ) || 0;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function byDateDesc(a,b) {
 | 
					 | 
				
			||||||
      return( a.safeStartDate.isBefore(b.safeStartDate) ) ? 1
 | 
					 | 
				
			||||||
        : ( a.safeStartDate.isAfter(b.safeStartDate) && -1 ) || 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Convert human-friendly dates into formal Moment.js dates for all collections.
 | 
					 | 
				
			||||||
  We don't want to lose the raw textual date as entered by the user, so we store
 | 
					 | 
				
			||||||
  the Moment-ified date as a separate property with a prefix of .safe. For ex:
 | 
					 | 
				
			||||||
  job.startDate is the date as entered by the user. job.safeStartDate is the
 | 
					 | 
				
			||||||
  parsed Moment.js date that we actually use in processing.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  function _parseDates() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var _fmt = require('./fluent-date').fmt;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.work && this.work.forEach( function(job) {
 | 
					 | 
				
			||||||
      job.safeStartDate = _fmt( job.startDate );
 | 
					 | 
				
			||||||
      job.safeEndDate = _fmt( job.endDate );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    this.education && this.education.forEach( function(edu) {
 | 
					 | 
				
			||||||
      edu.safeStartDate = _fmt( edu.startDate );
 | 
					 | 
				
			||||||
      edu.safeEndDate = _fmt( edu.endDate );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    this.volunteer && this.volunteer.forEach( function(vol) {
 | 
					 | 
				
			||||||
      vol.safeStartDate = _fmt( vol.startDate );
 | 
					 | 
				
			||||||
      vol.safeEndDate = _fmt( vol.endDate );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    this.awards && this.awards.forEach( function(awd) {
 | 
					 | 
				
			||||||
      awd.safeDate = _fmt( awd.date );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    this.publications && this.publications.forEach( function(pub) {
 | 
					 | 
				
			||||||
      pub.safeReleaseDate = _fmt( pub.releaseDate );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
  Export the Sheet function/ctor.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  module.exports = Sheet;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}());
 | 
					 | 
				
			||||||
							
								
								
									
										35
									
								
								src/core/status-codes.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/core/status-codes.coffee
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					###*
 | 
				
			||||||
 | 
					Status codes for HackMyResume.
 | 
				
			||||||
 | 
					@module core/status-codes
 | 
				
			||||||
 | 
					@license MIT. See LICENSE.MD for details.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports =
 | 
				
			||||||
 | 
					  success: 0
 | 
				
			||||||
 | 
					  themeNotFound: 1
 | 
				
			||||||
 | 
					  copyCss: 2
 | 
				
			||||||
 | 
					  resumeNotFound: 3
 | 
				
			||||||
 | 
					  missingCommand: 4
 | 
				
			||||||
 | 
					  invalidCommand: 5
 | 
				
			||||||
 | 
					  resumeNotFoundAlt: 6
 | 
				
			||||||
 | 
					  inputOutputParity: 7
 | 
				
			||||||
 | 
					  createNameMissing: 8
 | 
				
			||||||
 | 
					  pdfGeneration: 9
 | 
				
			||||||
 | 
					  missingPackageJSON: 10
 | 
				
			||||||
 | 
					  invalid: 11
 | 
				
			||||||
 | 
					  invalidFormat: 12
 | 
				
			||||||
 | 
					  notOnPath: 13
 | 
				
			||||||
 | 
					  readError: 14
 | 
				
			||||||
 | 
					  parseError: 15
 | 
				
			||||||
 | 
					  fileSaveError: 16
 | 
				
			||||||
 | 
					  generateError: 17
 | 
				
			||||||
 | 
					  invalidHelperUse: 18
 | 
				
			||||||
 | 
					  mixedMerge: 19
 | 
				
			||||||
 | 
					  invokeTemplate: 20
 | 
				
			||||||
 | 
					  compileTemplate: 21
 | 
				
			||||||
 | 
					  themeLoad: 22
 | 
				
			||||||
 | 
					  invalidParamCount: 23
 | 
				
			||||||
 | 
					  missingParam: 24
 | 
				
			||||||
 | 
					  createError: 25
 | 
				
			||||||
 | 
					  validateError: 26
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user