Генерация SCORM*

*без боли

Типы SCORM пакетов

  • Обычный (синяя цветовая гамма)

  • Для HeadHunter (фиолетовая цветовая гамма + 2 версии - полная и демо)

  • С локальными видео (Связной)

Ты говоришь, что надоело генерировать SCORM пакеты вручную и править файлы, но не делаешь автоматический генератор

Что хотелось?

  • Удобный интерфейс (или вообще хоть какой-нибудь)
  • Чтобы не нужно было вручную править файлы
  • Разные CSS-манифесты для разных пакетов

Не JavaScript единым!

  • GUI на Bash для интерфейса? 
  • Самому дорабатывать ruby-генератор?

Headhunter

Курсы с локальными видео

BASH GUI

 

#!/bin/bash

##########################################################################
#                                                                        #
# Возможные типы SCORM пакетов:                                          #
#                                                                        #
# common - обычный                                                       #
# hh - версия для HeadHunter (генерирует сразу и демо и полную версию)   #
# hh_full - версия для HeadHunter (полная)                               #
# hh_demo - версия для HeadHunter (урезанная, для демо-доступа)          #
# with_local_videos - версия с локальными видео                          #
#                                                                        #
##########################################################################

function show_generator_title {
  # Set a foreground colour using ANSI escape
  tput setaf 3

  echo "
    ███╗   ██╗███████╗████████╗ ██████╗ ██╗      ██████╗  ██████╗██╗   ██╗    ███████╗ ██████╗ ██████╗ ██████╗ ███╗   ███╗
    ████╗  ██║██╔════╝╚══██╔══╝██╔═══██╗██║     ██╔═══██╗██╔════╝╚██╗ ██╔╝    ██╔════╝██╔════╝██╔═══██╗██╔══██╗████╗ ████║
    ██╔██╗ ██║█████╗     ██║   ██║   ██║██║     ██║   ██║██║  ███╗╚████╔╝     ███████╗██║     ██║   ██║██████╔╝██╔████╔██║
    ██║╚██╗██║██╔══╝     ██║   ██║   ██║██║     ██║   ██║██║   ██║ ╚██╔╝      ╚════██║██║     ██║   ██║██╔══██╗██║╚██╔╝██║
    ██║ ╚████║███████╗   ██║   ╚██████╔╝███████╗╚██████╔╝╚██████╔╝  ██║       ███████║╚██████╗╚██████╔╝██║  ██║██║ ╚═╝ ██║
    ╚═╝  ╚═══╝╚══════╝   ╚═╝    ╚═════╝ ╚══════╝ ╚═════╝  ╚═════╝   ╚═╝       ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝

     ██████╗ ███████╗███╗   ██╗███████╗██████╗  █████╗ ████████╗ ██████╗ ██████╗     ██╗   ██╗   ██╗    ██████╗
    ██╔════╝ ██╔════╝████╗  ██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗    ██║   ██║  ███║   ██╔═████╗
    ██║  ███╗█████╗  ██╔██╗ ██║█████╗  ██████╔╝███████║   ██║   ██║   ██║██████╔╝    ██║   ██║  ╚██║   ██║██╔██║
    ██║   ██║██╔══╝  ██║╚██╗██║██╔══╝  ██╔══██╗██╔══██║   ██║   ██║   ██║██╔══██╗    ╚██╗ ██╔╝   ██║   ████╔╝██║
    ╚██████╔╝███████╗██║ ╚████║███████╗██║  ██║██║  ██║   ██║   ╚██████╔╝██║  ██║     ╚████╔╝    ██║██╗╚██████╔╝
     ╚═════╝ ╚══════╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝      ╚═══╝     ╚═╝╚═╝ ╚═════╝
                                                                                                                          "

  tput sgr0
}

function show_main_menu {
  # clear the screen
  tput clear

  # Move cursor to screen location X,Y (top left is 0,0)
  tput cup 1 15

  show_generator_title

  tput cup 17 15
  # Set reverse video mode
  tput rev
  echo "WHAT DO YOU WANT TO GENERATE?"
  tput sgr0

  tput cup 19 15
  echo "1. Common SCORM package"

  tput cup 20 15
  echo "2. HeadHunter SCORM package (all versions)"

  tput cup 21 15
  echo "3. HeadHunter SCORM package (full access version)"

  tput cup 22 15
  echo "4. HeadHunter SCORM package (demo access version)"

  tput cup 23 15
  echo "5. SCORM package with local videos"

  tput cup 24 15
  echo "6. I want to quit"

  # Set bold mode
  tput bold
  tput cup 26 15

  # Получаем выбранный пункт
  read -p "Enter your choice [1-6] " choice

  tput clear
  tput sgr0
  #tput rc

  case "$choice" in
    "1") show_generator_menu "GENERATE COMMON SCORM PACKAGE. PLEASE WAIT..." "common"
        ;;
    "2") show_generator_menu "GENERATE HEADHUNTER SCORM PACKAGE (all versions). PLEASE WAIT..." "hh"
        ;;
    "3") show_generator_menu "GENERATE HEADHUNTER SCORM PACKAGE (full access version). PLEASE WAIT..." "hh_full"
        ;;
    "4") show_generator_menu "GENERATE HEADHUNTER SCORM PACKAGE (demo access version). PLEASE WAIT..." "hh_demo"
        ;;
    "5") # Удаляем файл-список названий видео для варианта SCORM с локальными видео-файлами
         rm public/scorm/with_local_videos/video_names.txt
         show_generator_menu "GENERATE SCORM PACKAGE WITH LOCAL VIDEOS. PLEASE WAIT..." "with_local_videos"
         # Показываем новый список названий видео-файлов
         echo ""
         tput setaf 2
         cat public/scorm/with_local_videos/video_names.txt
         tput sgr0
         echo ""
        ;;
    "6") echo "Exit from SCORM generator..."
        exit 0
        ;;
    *) echo "Wrong choice... Please try again."
       show_main_menu
       ;;
  esac
}

function show_generator_menu {
  # $1 = Заголовок
  # $2 = тип пакета
  tput clear

  tput cup 1 15

  show_generator_title

  tput cup 17 15

  tput rev
  echo $1
  tput sgr0

  tput bold
  tput cup 19 15

  tput sgr0

  tput bold
  tput cup 21 15

  compile_scorm_assets $2

  generate_scorm_packages
}

function compile_scorm_assets {
  echo 'Start assets compilation...'

  tput sgr0

  scorm_type=$1

  case "$scorm_type" in
    "common")
      RAILS_ENV=production rake scorm:generate
      ;;
    "hh")
      RAILS_ENV=production rake scorm:generate_hh
      ;;
    "hh_full")
      RAILS_ENV=production rake scorm:generate_hh_full
      ;;
    "hh_demo")
      RAILS_ENV=production rake scorm:generate_hh_demo
      ;;
    "with_local_videos")
      RAILS_ENV=production rake scorm:generate_with_local_videos
      ;;
    *) echo "Error. SCORM package type '$scorm_type' doesn't exist."
       exit 0
       ;;
  esac


  extract_current_cursor_position last_position
  last_line_number=${last_position[0]}

  tput cup $[$last_line_number + 1] 15

  tput bold
  echo 'End assets compilation'
}

function generate_scorm_packages {
  extract_current_cursor_position last_position
  last_line_number=${last_position[0]}

  tput cup $[$last_line_number + 3] 15

  echo 'Start SCORM package generation... (press Ctrl+C to terminate Sidekiq and return back to script)'

  tput sgr0

  sidekiq -e production

  extract_current_cursor_position last_position
  last_line_number=${last_position[0]}

  tput cup $[$last_line_number + 1] 15

  tput bold
  echo 'End SCORM package generation (using sidekiq)'
}

#############################
#  Вспомогательные функции  #
#############################

# http://askubuntu.com/questions/366103/saving-more-corsor-positions-with-tput-in-bash-terminal
function extract_current_cursor_position {
    export $1
    exec < /dev/tty
    oldstty=$(stty -g)
    stty raw -echo min 0
    echo -en "\033[6n" > /dev/tty
    IFS=';' read -r -d R -a pos
    stty $oldstty
    eval "$1[0]=$((${pos[0]:2} - 2))"
    eval "$1[1]=$((${pos[1]} - 1))"
}

show_main_menu

Запуск

sudo chmod a+x generator.sh
./generator.sh

How to

Было

How to

Стало

Трудности

  • Вспомнить Bash
  • Изучать Ruby (см. след. слайд)
  • Удалять лишние манифесты по регулярке
  • Находить в JSON ссылки на картинки и сохранение картинок

Ruby autoloading constant bug

hain.rb:132:in `invoke'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/sidekiq-3.4.1/lib/sidekiq/processor.rb:51:in `block in process'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/sidekiq-3.4.1/lib/sidekiq/processor.rb:98:in `stats'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/sidekiq-3.4.1/lib/sidekiq/processor.rb:50:in `process'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/calls.rb:26:in `public_send'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/calls.rb:26:in `dispatch'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/calls.rb:122:in `dispatch'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/cell.rb:60:in `block in invoke'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/cell.rb:71:in `block in task'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/actor.rb:357:in `block in task'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/tasks.rb:57:in `block in initialize'", "/Users/pavel/.rvm/gems/ruby-2.1.5/gems/celluloid-0.16.0/lib/celluloid/tasks/task_fiber.rb:15:in `block in create'"], "retried_at"=>1453282128.844913, "processor"=>"pkomyagin.local:85019"}
2016-01-20T09:28:48.861Z 85019 TID-owin3mllg WARN: RuntimeError: Circular dependency detected while autoloading constant PackageBuilding::HtmlContentBuilder
2016-01-20T09:28:48.861Z 85019 TID-owin3mllg WARN: /Users/pavel/.rvm/gems/ruby-2.1.5/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:492:in `load_missing_constant'
/Users/pavel/.rvm/gems/ruby-2.1.5/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/pavel/work/netology_scorm/lib/package_building/package_building/workflow.rb:91:in `html_content_builder'
/Users/pavel/work/netology_scorm/lib/package_building/package_building/workflow.rb:60:in `block in perform_scorm_hh_demo'
/Users/pavel/work/netology_scorm/lib/package_building/package_building/data_packager.rb:37:in `block in pack_html_content'
/Users/pavel/.rvm/rubies/ruby-2.1.5/lib/ruby/2.1.0/open-uri.rb:36:in `open'
/Users/pavel/.rvm/rubies/ruby-2.1.5/lib/ruby/2.1.0/open-uri.rb:36:in `open'
/Users/pavel/work/netology_scorm/lib/package_building/package_building/data_packager.rb:36:in `pack_html_content'
/Users/pavel/work/netology_scorm/lib/package_building/package_building/workflow.rb:60:in `perform_scorm_hh_demo'
/Users/pavel/work/netology_scorm/lib/package_building/package_building/workflow.rb:21:in `perform'
/Users/pavel/work/netology_scorm/app/jobs/package_building/single_scorm_generation_job.rb:6:in `perform'
def data_packager
  @data_packager ||= ::PackageBuilding::DataPackager.new(@scorm_type)
end

def course_info_collector
  @course_info_collector ||= ::PackageBuilding::CourseInfoCollector.new
end

def ims_manifest_builder
  course_json = JSON.parse(course_info)
  @ims_manifest_builder ||= ::PackageBuilding::ImsManifestBuilder.new.build_document(course_json)
end

def html_content_builder
  @html_content_builder ||= ::PackageBuilding::HtmlContentBuilder.new(@scorm_type)
end

def course_info
  @course_info ||= course_info_collector.fetch_course_info(course_id)
end

Приятной генерации

Генерация SCORM (без боли)

By Pavel Komiagin

Генерация SCORM (без боли)

  • 214